diff --git a/lua/wikis/commons/Widget/Component.lua b/lua/wikis/commons/Widget/Component.lua index d69a28df5e7..9655429a36a 100644 --- a/lua/wikis/commons/Widget/Component.lua +++ b/lua/wikis/commons/Widget/Component.lua @@ -19,8 +19,8 @@ ---@alias ContextDef {defaultValue: T} ---@alias Component

fun(props?: P, context: Context?): VNode

----@alias ContextComponent Component<{def: ContextDef, value: T, children: Renderable[]}> ----@alias HtmlComponent Component<{classes?: string[], css?: table, attributes?: table, children?: Renderable[]}> +---@alias ContextComponent Component<{def: ContextDef, value: T, children?: Renderable|Renderable[]?}> +---@alias HtmlComponent Component<{classes?: string[], css?: table, attributes?: table, children?: Renderable|Renderable[]}> local Lua = require('Module:Lua') local Renderer = Lua.import('Module:Widget/Renderer') @@ -40,17 +40,48 @@ ComponentCore.VNodeMT = { } } +--- Highly efficient check for if a node is actually an array of nodes, or just a single node +---@param node Renderable|Renderable[]|nil +---@return boolean +local function isSingleNode(node) + if type(node) ~= 'table' then + return true + end + + -- VNodes always have this key + if node.renderFn ~= nil then + return true + end + + ---@cast node -VNode + + -- Widget (render) and mw.html (_build) + if node.render ~= nil or node._build ~= nil then + return true + end + + ---@cast node -Html + ---@cast node -Widget + + -- Array is the only allowed type of Renderable left + return false +end + -- Component Definitions ComponentCore.ComponentMT = { __call = function(self, props) props = props or {} -- Apply DefaultProps via lightweight metatable - -- Only shallow default props allowed + -- Only shallow default props allowed, or empty tables if self.defaultProps then setmetatable(props, { __index = self.defaultProps }) end + if isSingleNode(props.children) then + props.children = { props.children } + end + return setmetatable({ renderFn = self.renderFn, props = props diff --git a/lua/wikis/commons/Widget/Contexts/Table2.lua b/lua/wikis/commons/Widget/Contexts/Table2.lua index f4a63f1535b..043ad192834 100644 --- a/lua/wikis/commons/Widget/Contexts/Table2.lua +++ b/lua/wikis/commons/Widget/Contexts/Table2.lua @@ -6,13 +6,11 @@ -- local Lua = require('Module:Lua') - -local Class = Lua.import('Module:Class') -local Context = Lua.import('Module:Widget/Context') +local Context = Lua.import('Module:Widget/ComponentContext') return { - BodyStripe = Class.new(Context), - ColumnContext = Class.new(Context), - HeaderRowKind = Class.new(Context), - Section = Class.new(Context), + BodyStripe = Context.create('disabled'), + ColumnContext = Context.create({}), + HeaderRowKind = Context.create('title'), + Section = Context.create('head'), } diff --git a/lua/wikis/commons/Widget/Table2/All.lua b/lua/wikis/commons/Widget/Table2/All.lua index a2b7bed306f..181941e515c 100644 --- a/lua/wikis/commons/Widget/Table2/All.lua +++ b/lua/wikis/commons/Widget/Table2/All.lua @@ -5,15 +5,15 @@ -- Please see https://github.com/Liquipedia/Lua-Modules to contribute -- -local Widgets = {} +local Components = {} local Lua = require('Module:Lua') -Widgets.Table = Lua.import('Module:Widget/Table2/Table') -Widgets.TableHeader = Lua.import('Module:Widget/Table2/TableHeader') -Widgets.TableBody = Lua.import('Module:Widget/Table2/TableBody') -Widgets.Row = Lua.import('Module:Widget/Table2/Row') -Widgets.CellHeader = Lua.import('Module:Widget/Table2/CellHeader') -Widgets.Cell = Lua.import('Module:Widget/Table2/Cell') +Components.Table = Lua.import('Module:Widget/Table2/Table') +Components.TableHeader = Lua.import('Module:Widget/Table2/TableHeader') +Components.TableBody = Lua.import('Module:Widget/Table2/TableBody') +Components.Row = Lua.import('Module:Widget/Table2/Row') +Components.CellHeader = Lua.import('Module:Widget/Table2/CellHeader') +Components.Cell = Lua.import('Module:Widget/Table2/Cell') -return Widgets +return Components diff --git a/lua/wikis/commons/Widget/Table2/Cell.lua b/lua/wikis/commons/Widget/Table2/Cell.lua index 2b5bd267f50..1775441e6c4 100644 --- a/lua/wikis/commons/Widget/Table2/Cell.lua +++ b/lua/wikis/commons/Widget/Table2/Cell.lua @@ -7,15 +7,15 @@ local Lua = require('Module:Lua') -local Class = Lua.import('Module:Class') +local Component = Lua.import('Module:Widget/Component') +local Context = Lua.import('Module:Widget/ComponentContext') -local Widget = Lua.import('Module:Widget') -local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Html = Lua.import('Module:Widget/Html') local Table2Contexts = Lua.import('Module:Widget/Contexts/Table2') local ColumnUtil = Lua.import('Module:Widget/Table2/ColumnUtil') ---@class Table2CellProps ----@field children Renderable[]? +---@field children Renderable[] ---@field align ('left'|'right'|'center')? ---@field shrink (string|number|boolean)? ---@field nowrap (string|number|boolean)? @@ -29,24 +29,14 @@ local ColumnUtil = Lua.import('Module:Widget/Table2/ColumnUtil') ---@field css {[string]: string|number|nil}? ---@field attributes {[string]: any}? ----@class Table2Cell: Widget ----@operator call(Table2CellProps): Table2Cell ----@field props Table2CellProps -local Table2Cell = Class.new(Widget) - -Table2Cell.defaultProps = { - nowrap = true, -} - ----@return Widget -function Table2Cell:render() - local props = self.props - - local columns = self:useContext(Table2Contexts.ColumnContext) +---@param props Table2CellProps +---@return Renderable +local function Table2Cell(props, context) + local columns = Context.read(context, Table2Contexts.ColumnContext) -- Skip context lookups and property merging if there are no column definitions - if not columns then - return HtmlWidgets.Td{ + if #columns == 0 then + return Html.Td{ attributes = ColumnUtil.buildCellAttributes( props.align, props.nowrap, @@ -76,7 +66,7 @@ function Table2Cell:render() ColumnUtil.buildAttributes(mergedProps) ) - return HtmlWidgets.Td{ + return Html.Td{ classes = mergedProps.classes, css = css, attributes = attributes, @@ -84,4 +74,9 @@ function Table2Cell:render() } end -return Table2Cell +return Component.component( + Table2Cell, + { + nowrap = true, + } +) diff --git a/lua/wikis/commons/Widget/Table2/CellHeader.lua b/lua/wikis/commons/Widget/Table2/CellHeader.lua index 2e4a7547156..3b4c3d11977 100644 --- a/lua/wikis/commons/Widget/Table2/CellHeader.lua +++ b/lua/wikis/commons/Widget/Table2/CellHeader.lua @@ -7,16 +7,17 @@ local Lua = require('Module:Lua') -local Class = Lua.import('Module:Class') +local Component = Lua.import('Module:Widget/Component') +local Context = Lua.import('Module:Widget/ComponentContext') + local Logic = Lua.import('Module:Logic') -local Widget = Lua.import('Module:Widget') -local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Html = Lua.import('Module:Widget/Html') local Table2Contexts = Lua.import('Module:Widget/Contexts/Table2') local ColumnUtil = Lua.import('Module:Widget/Table2/ColumnUtil') ---@class Table2CellHeaderProps ----@field children Renderable[]? +---@field children Renderable[] ---@field section 'head'|'body'|'subhead'? ---@field align ('left'|'right'|'center')? ---@field shrink (string|number|boolean)? @@ -33,29 +34,22 @@ local ColumnUtil = Lua.import('Module:Widget/Table2/ColumnUtil') ---@field rowspan integer|string? ---@field columnIndex integer|string? ----@class Table2CellHeader: Widget ----@operator call(Table2CellHeaderProps): Table2CellHeader ----@field props Table2CellHeaderProps -local Table2CellHeader = Class.new(Widget) - ----@return Widget -function Table2CellHeader:render() - local props = self.props - - local columns = self:useContext(Table2Contexts.ColumnContext) - local section = props.section or self:useContext(Table2Contexts.Section) +---@param props Table2CellHeaderProps +---@return Renderable +local function Table2CellHeader(props, context) + local columns = Context.read(context, Table2Contexts.ColumnContext) + local section = props.section or Context.read(context, Table2Contexts.Section) local children = props.children - if section == 'subhead' then - children = {HtmlWidgets.Div{ + children = {Html.Div{ classes = {'table2__subheader-cell'}, children = props.children, }} end -- Skip context lookups and property merging if there are no column definitions - if not columns then + if #columns == 0 then local align = props.align local attributes = props.attributes or {} if align == 'right' or align == 'center' then @@ -72,7 +66,7 @@ function Table2CellHeader:render() attributes ) - return HtmlWidgets.Th{ + return Html.Th{ attributes = attributes, children = children, } @@ -108,7 +102,7 @@ function Table2CellHeader:render() attributes ) - return HtmlWidgets.Th{ + return Html.Th{ classes = mergedProps.classes, css = css, attributes = attributes, @@ -116,4 +110,6 @@ function Table2CellHeader:render() } end -return Table2CellHeader +return Component.component( + Table2CellHeader +) diff --git a/lua/wikis/commons/Widget/Table2/Row.lua b/lua/wikis/commons/Widget/Table2/Row.lua index d3521447da8..68535b58a0c 100644 --- a/lua/wikis/commons/Widget/Table2/Row.lua +++ b/lua/wikis/commons/Widget/Table2/Row.lua @@ -6,38 +6,34 @@ -- local Lua = require('Module:Lua') +local Component = Lua.import('Module:Widget/Component') +local Context = Lua.import('Module:Widget/ComponentContext') local Array = Lua.import('Module:Array') -local Class = Lua.import('Module:Class') local Logic = Lua.import('Module:Logic') local MathUtil = Lua.import('Module:MathUtil') -local Widget = Lua.import('Module:Widget') local Table2Contexts = Lua.import('Module:Widget/Contexts/Table2') local Table2Cell = Lua.import('Module:Widget/Table2/Cell') local Table2CellHeader = Lua.import('Module:Widget/Table2/CellHeader') local WidgetUtil = Lua.import('Module:Widget/Util') -local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Html = Lua.import('Module:Widget/Html') ---@class Table2RowProps ----@field children Renderable[]? +---@field children Renderable[] ---@field section 'head'|'body'|'subhead'? ---@field classes string[]? ---@field css {[string]: string|number|nil}? ---@field attributes {[string]: any}? ---@field highlighted (string|number|boolean)? ----@class Table2Row: Widget ----@operator call(Table2RowProps): Table2Row ----@field props Table2RowProps -local Table2Row = Class.new(Widget) - ----@return Widget -function Table2Row:render() - local props = self.props - local section = props.section or self:useContext(Table2Contexts.Section) - local headerRowKind = self:useContext(Table2Contexts.HeaderRowKind) - local bodyStripe = self:useContext(Table2Contexts.BodyStripe) +---@param props Table2RowProps +---@param context Context +---@return Renderable +local function Table2Row(props, context) + local section = props.section or Context.read(context, Table2Contexts.Section) + local headerRowKind = Context.read(context, Table2Contexts.HeaderRowKind) + local bodyStripe = Context.read(context, Table2Contexts.BodyStripe) local sectionClass = 'table2__row--body' if section == 'head' or section == 'subhead' then @@ -69,9 +65,12 @@ function Table2Row:render() local children = props.children or {} - local columns = self:useContext(Table2Contexts.ColumnContext) - if section == 'subhead' and columns and #children == 1 and Class.instanceOf(children[1], Table2CellHeader) then - local singleCell = children[1] --[[@as Table2CellHeader]] + local columns = Context.read(context, Table2Contexts.ColumnContext) + if section == 'subhead' and #columns > 0 and #children == 1 and + ---@diagnostic disable-next-line: undefined-field + type(children[1]) == 'table' and children[1].renderFn == Table2CellHeader.renderFn then + + local singleCell = children[1] if singleCell.props.colspan == nil then singleCell.props.colspan = #columns end @@ -79,8 +78,11 @@ function Table2Row:render() local columnIndex = 1 local indexedChildren = Array.map(children, function(child) - if Class.instanceOf(child, Table2Cell) or Class.instanceOf(child, Table2CellHeader) then - local cellChild = child --[[@as Table2Cell|Table2CellHeader]] + if type(child) == 'table' and + ---@diagnostic disable-next-line: undefined-field + (child.renderFn == Table2Cell.renderFn or child.renderFn == Table2CellHeader.renderFn) then + + local cellChild = child local explicitIndex = MathUtil.toInteger(cellChild.props.columnIndex) if explicitIndex and explicitIndex >= 1 then columnIndex = math.max(columnIndex, explicitIndex) @@ -101,8 +103,10 @@ function Table2Row:render() local trChildren = indexedChildren if section == 'subhead' then trChildren = Array.map(trChildren, function(child) - if Class.instanceOf(child, Table2CellHeader) then - return Table2Contexts.Section{ + ---@diagnostic disable-next-line: undefined-field + if type(child) == 'table' and child.renderFn == Table2Cell.renderFn then + return Context.Provider{ + def = Table2Contexts.Section, value = 'subhead', children = {child}, } @@ -111,7 +115,7 @@ function Table2Row:render() end) end - return HtmlWidgets.Tr{ + return Html.Tr{ classes = WidgetUtil.collect(sectionClass, kindClass, stripeClass, highlightClass, props.classes), css = props.css, attributes = props.attributes, @@ -119,4 +123,6 @@ function Table2Row:render() } end -return Table2Row +return Component.component( + Table2Row +) \ No newline at end of file diff --git a/lua/wikis/commons/Widget/Table2/Table.lua b/lua/wikis/commons/Widget/Table2/Table.lua index d05bdce3e26..7cae44950b8 100644 --- a/lua/wikis/commons/Widget/Table2/Table.lua +++ b/lua/wikis/commons/Widget/Table2/Table.lua @@ -6,14 +6,14 @@ -- local Lua = require('Module:Lua') +local Component = Lua.import('Module:Widget/Component') +local Context = Lua.import('Module:Widget/ComponentContext') local Array = Lua.import('Module:Array') -local Class = Lua.import('Module:Class') local Logic = Lua.import('Module:Logic') -local Widget = Lua.import('Module:Widget') local WidgetUtil = Lua.import('Module:Widget/Util') -local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Html = Lua.import('Module:Widget/Html') local Table2Contexts = Lua.import('Module:Widget/Contexts/Table2') ---@class Table2ColumnDef @@ -30,7 +30,7 @@ local Table2Contexts = Lua.import('Module:Widget/Contexts/Table2') ---@field attributes {[string]: any}? ---@class Table2Props ----@field children Renderable[]? +---@field children Renderable[] ---@field variant 'generic'|'themed'? ---@field sortable (string|number|boolean)? ---@field striped (string|number|boolean)? @@ -44,23 +44,10 @@ local Table2Contexts = Lua.import('Module:Widget/Contexts/Table2') ---@field attributes {[string]: any}? ---@field tableAttributes {[string]: any}? ----@class Table2: Widget ----@operator call(Table2Props): Table2 ----@field props Table2Props -local Table2 = Class.new(Widget) - -Table2.defaultProps = { - variant = 'generic', - sortable = false, - striped = true, - classes = {}, - columns = {}, -} - ----@return Widget[] -function Table2:render() - local props = self.props - +---@param props Table2Props +---@param context Context +---@return Renderable[] +local function Table2(props, context) if props.columns and #props.columns > 0 then Array.forEach(props.columns, function(columnDef, columnIndex) assert(not (Logic.readBool(columnDef.shrink) and columnDef.width), @@ -77,48 +64,50 @@ function Table2:render() props.tableClasses ) - local captionNode = props.caption and HtmlWidgets.Div{ + local captionNode = props.caption and Html.Div{ classes = {'table2__caption'}, children = props.caption, } or nil - local titleNode = props.title and HtmlWidgets.Div{ + local titleNode = props.title and Html.Div{ classes = {'table2__title'}, children = props.title, } or nil local tableChildren = props.children if props.columns and #props.columns > 0 then - tableChildren = {Table2Contexts.ColumnContext{ + tableChildren = {Context.Provider{ + def = Table2Contexts.ColumnContext, value = props.columns, children = tableChildren, }} end if Logic.readBool(props.striped) then - tableChildren = {Table2Contexts.BodyStripe{ + tableChildren = {Context.Provider{ + def = Table2Contexts.BodyStripe, value = true, children = tableChildren, }} end - local tableNode = HtmlWidgets.Table{ + local tableNode = Html.Table{ attributes = props.tableAttributes, classes = tableClasses, children = tableChildren, } - local containerNode = HtmlWidgets.Div{ + local containerNode = Html.Div{ classes = {'table2__container'}, children = {tableNode}, } - local footerNode = props.footer and HtmlWidgets.Div{ + local footerNode = props.footer and Html.Div{ classes = {'table2__footer'}, children = props.footer, } or nil - local tableWrapperNode = HtmlWidgets.Div{ + local tableWrapperNode = Html.Div{ classes = wrapperClasses, css = props.css, attributes = props.attributes, @@ -128,4 +117,13 @@ function Table2:render() return WidgetUtil.collect(captionNode, tableWrapperNode) end -return Table2 +return Component.component( + Table2, + { + variant = 'generic', + sortable = false, + striped = true, + classes = {}, + columns = {}, + } +) diff --git a/lua/wikis/commons/Widget/Table2/TableBody.lua b/lua/wikis/commons/Widget/Table2/TableBody.lua index f1ff55f8c23..856c0b3209a 100644 --- a/lua/wikis/commons/Widget/Table2/TableBody.lua +++ b/lua/wikis/commons/Widget/Table2/TableBody.lua @@ -8,31 +8,29 @@ local Lua = require('Module:Lua') local Array = Lua.import('Module:Array') -local Class = Lua.import('Module:Class') +local Component = Lua.import('Module:Widget/Component') +local Context = Lua.import('Module:Widget/ComponentContext') +local FnUtil = Lua.import('Module:FnUtil') local MathUtil = Lua.import('Module:MathUtil') -local Widget = Lua.import('Module:Widget') local Table2Contexts = Lua.import('Module:Widget/Contexts/Table2') local Table2Cell = Lua.import('Module:Widget/Table2/Cell') local Table2CellHeader = Lua.import('Module:Widget/Table2/CellHeader') local Table2Row = Lua.import('Module:Widget/Table2/Row') ---@class Table2BodyProps ----@field children Renderable[]? +---@field children Renderable[] ----@class Table2Body: Widget ----@operator call(Table2BodyProps): Table2Body ----@field props Table2BodyProps -local Table2Body = Class.new(Widget) - ----@return Widget -function Table2Body:render() - local props = self.props +---@param props Table2BodyProps +---@param context Context +---@return Renderable +local function Table2Body(props, context) local children = props.children or {} - local stripeEnabled = self:useContext(Table2Contexts.BodyStripe) - if stripeEnabled == nil then - return Table2Contexts.Section{ + local stripeEnabled = Context.read(context, Table2Contexts.BodyStripe) + if stripeEnabled == 'disabled' then + return Context.Provider{ + def = Table2Contexts.Section, value = 'body', children = children, } @@ -46,30 +44,33 @@ function Table2Body:render() stripe = stripe == 'even' and 'odd' or 'even' end - local function getRowMaxRowspan(row) - if row and row._cachedMaxRowspan then - return row._cachedMaxRowspan - end - + ---@param row VNode<{children: Renderable|Renderable[]}> + ---@return integer + local getRowMaxRowspan = FnUtil.memoize(function(row) local rowChildren = (row and row.props and row.props.children) or {} + if type(rowChildren) ~= 'table' then + rowChildren = {rowChildren} + end local maxRowspan = 1 + Array.forEach(rowChildren, function(child) - if Class.instanceOf(child, Table2Cell) or Class.instanceOf(child, Table2CellHeader) then + if type(child) == 'table' + ---@diagnostic disable-next-line: undefined-field + and (child.renderFn == Table2Cell.renderFn or child.renderFn == Table2CellHeader.renderFn) then + local rowspan = MathUtil.toInteger(child.props.rowspan) or 1 rowspan = math.max(rowspan, 1) maxRowspan = math.max(maxRowspan, rowspan) end end) - if row then - row._cachedMaxRowspan = maxRowspan - end - return maxRowspan - end + end) Array.forEach(children, function(child) - if Class.instanceOf(child, Table2Row) then + ---@diagnostic disable-next-line: undefined-field + if type(child) == 'table' and child.renderFn == Table2Row.renderFn then + ---@cast child VNode if groupRemaining == 0 then toggleStripe() end @@ -77,7 +78,8 @@ function Table2Body:render() local maxRowspan = getRowMaxRowspan(child) groupRemaining = math.max(groupRemaining, maxRowspan) - table.insert(stripedChildren, Table2Contexts.BodyStripe{ + table.insert(stripedChildren, Context.Provider{ + def = Table2Contexts.BodyStripe, value = stripe, children = {child}, }) @@ -88,10 +90,13 @@ function Table2Body:render() end end) - return Table2Contexts.Section{ + return Context.Provider{ + def = Table2Contexts.Section, value = 'body', children = stripedChildren, } end -return Table2Body +return Component.component( + Table2Body +) diff --git a/lua/wikis/commons/Widget/Table2/TableHeader.lua b/lua/wikis/commons/Widget/Table2/TableHeader.lua index b4d9b3b1d9f..b449a40b399 100644 --- a/lua/wikis/commons/Widget/Table2/TableHeader.lua +++ b/lua/wikis/commons/Widget/Table2/TableHeader.lua @@ -8,40 +8,37 @@ local Lua = require('Module:Lua') local Array = Lua.import('Module:Array') -local Class = Lua.import('Module:Class') +local Context = Lua.import('Module:Widget/ComponentContext') +local Component = Lua.import('Module:Widget/Component') -local Widget = Lua.import('Module:Widget') local Table2Row = Lua.import('Module:Widget/Table2/Row') local Table2Contexts = Lua.import('Module:Widget/Contexts/Table2') ---@class Table2HeaderProps ----@field children Renderable[]? +---@field children Renderable[] ----@class Table2Header: Widget ----@operator call(Table2HeaderProps): Table2Header ----@field props Table2HeaderProps -local Table2Header = Class.new(Widget) - ----@return Widget -function Table2Header:render() - local props = self.props +---@param props Table2HeaderProps +---@param context Context +---@return Renderable +local function Table2Header(props, context) local rowCount = 0 local children = Array.map(props.children or {}, function(child) - if Class.instanceOf(child, Table2Row) then + ---@diagnostic disable-next-line: undefined-field + if type(child) == 'table' and child.renderFn == Table2Row.renderFn then rowCount = rowCount + 1 local kind = rowCount == 1 and 'title' or 'columns' - child = Table2Contexts.HeaderRowKind{ - value = kind, - children = {child}, - } + child = Context.Provider{def = Table2Contexts.HeaderRowKind, value = kind, children = {child}} end return child end) - return Table2Contexts.Section{ + return Context.Provider{ + def = Table2Contexts.Section, value = 'head', children = children, } end -return Table2Header +return Component.component( + Table2Header +)