disambiguate, reintroduce removed method

This commit is contained in:
Michael Freno
2025-10-10 22:54:28 -04:00
parent 7aed6bef69
commit 2c68260340
5 changed files with 117 additions and 88 deletions

View File

@@ -27,6 +27,27 @@ function Color:toRGBA()
return self.r, self.g, self.b, self.a return self.r, self.g, self.b, self.a
end end
--- Convert hex string to color
---@param hexWithTag string -- e.g. "#RRGGBB" or "#RRGGBBAA"
---@return Color
function Color.fromHex(hexWithTag)
local hex = hexWithTag:gsub("#", "")
if #hex == 6 then
local r = tonumber("0x" .. hex:sub(1, 2)) or 0
local g = tonumber("0x" .. hex:sub(3, 4)) or 0
local b = tonumber("0x" .. hex:sub(5, 6)) or 0
return Color.new(r, g, b, 1)
elseif #hex == 8 then
local r = tonumber("0x" .. hex:sub(1, 2)) or 0
local g = tonumber("0x" .. hex:sub(3, 4)) or 0
local b = tonumber("0x" .. hex:sub(5, 6)) or 0
local a = tonumber("0x" .. hex:sub(7, 8)) / 255
return Color.new(r, g, b, a)
else
error("Invalid hex string")
end
end
local enums = { local enums = {
---@enum TextAlign ---@enum TextAlign
TextAlign = { START = "start", CENTER = "center", END = "end", JUSTIFY = "justify" }, TextAlign = { START = "start", CENTER = "center", END = "end", JUSTIFY = "justify" },
@@ -83,14 +104,14 @@ local enums = {
FlexWrap = { NOWRAP = "nowrap", WRAP = "wrap", WRAP_REVERSE = "wrap-reverse" }, FlexWrap = { NOWRAP = "nowrap", WRAP = "wrap", WRAP_REVERSE = "wrap-reverse" },
---@enum GridAutoFlow ---@enum GridAutoFlow
GridAutoFlow = { ROW = "row", COLUMN = "column", ROW_DENSE = "row dense", COLUMN_DENSE = "column dense" }, GridAutoFlow = { ROW = "row", COLUMN = "column", ROW_DENSE = "row dense", COLUMN_DENSE = "column dense" },
---@enum JustifyItems ---@enum GridJustifyItems
JustifyItems = { GridJustifyItems = {
STRETCH = "stretch", STRETCH = "stretch",
START = "start", START = "start",
END = "end", END = "end",
CENTER = "center", CENTER = "center",
}, },
---@enum AlignContent (Grid) ---@enum GridAlignContent
GridAlignContent = { GridAlignContent = {
STRETCH = "stretch", STRETCH = "stretch",
START = "start", START = "start",
@@ -100,7 +121,7 @@ local enums = {
SPACE_AROUND = "space-around", SPACE_AROUND = "space-around",
SPACE_EVENLY = "space-evenly", SPACE_EVENLY = "space-evenly",
}, },
---@enum JustifyContent (Grid) ---@enum GridJustifyContent
GridJustifyContent = { GridJustifyContent = {
STRETCH = "stretch", STRETCH = "stretch",
START = "start", START = "start",
@@ -112,7 +133,7 @@ local enums = {
}, },
} }
local Positioning, FlexDirection, JustifyContent, AlignContent, AlignItems, TextAlign, AlignSelf, JustifySelf, FlexWrap, GridAutoFlow, JustifyItems, GridAlignContent, GridJustifyContent = local Positioning, FlexDirection, JustifyContent, AlignContent, AlignItems, TextAlign, AlignSelf, JustifySelf, FlexWrap, GridAutoFlow, GridJustifyItems, GridAlignContent, GridJustifyContent =
enums.Positioning, enums.Positioning,
enums.FlexDirection, enums.FlexDirection,
enums.JustifyContent, enums.JustifyContent,
@@ -123,7 +144,7 @@ local Positioning, FlexDirection, JustifyContent, AlignContent, AlignItems, Text
enums.JustifySelf, enums.JustifySelf,
enums.FlexWrap, enums.FlexWrap,
enums.GridAutoFlow, enums.GridAutoFlow,
enums.JustifyItems, enums.GridJustifyItems,
enums.GridAlignContent, enums.GridAlignContent,
enums.GridJustifyContent enums.GridJustifyContent
@@ -290,43 +311,43 @@ function Grid.parseTrackSize(trackSize)
if type(trackSize) == "number" then if type(trackSize) == "number" then
return { type = "px", value = trackSize } return { type = "px", value = trackSize }
end end
if type(trackSize) ~= "string" then if type(trackSize) ~= "string" then
return { type = "auto" } return { type = "auto" }
end end
-- Handle auto -- Handle auto
if trackSize == "auto" then if trackSize == "auto" then
return { type = "auto" } return { type = "auto" }
end end
-- Handle min-content and max-content -- Handle min-content and max-content
if trackSize == "min-content" then if trackSize == "min-content" then
return { type = "min-content" } return { type = "min-content" }
end end
if trackSize == "max-content" then if trackSize == "max-content" then
return { type = "max-content" } return { type = "max-content" }
end end
-- Handle fr units -- Handle fr units
local frValue = trackSize:match("^([%d%.]+)fr$") local frValue = trackSize:match("^([%d%.]+)fr$")
if frValue then if frValue then
return { type = "fr", value = tonumber(frValue) } return { type = "fr", value = tonumber(frValue) }
end end
-- Handle percentage -- Handle percentage
local percentValue = trackSize:match("^([%d%.]+)%%$") local percentValue = trackSize:match("^([%d%.]+)%%$")
if percentValue then if percentValue then
return { type = "%", value = tonumber(percentValue) } return { type = "%", value = tonumber(percentValue) }
end end
-- Handle pixel values -- Handle pixel values
local pxValue = trackSize:match("^([%d%.]+)px$") local pxValue = trackSize:match("^([%d%.]+)px$")
if pxValue then if pxValue then
return { type = "px", value = tonumber(pxValue) } return { type = "px", value = tonumber(pxValue) }
end end
-- Handle minmax(min, max) -- Handle minmax(min, max)
local minStr, maxStr = trackSize:match("^minmax%s*%(([^,]+),%s*([^)]+)%)$") local minStr, maxStr = trackSize:match("^minmax%s*%(([^,]+),%s*([^)]+)%)$")
if minStr and maxStr then if minStr and maxStr then
@@ -334,7 +355,7 @@ function Grid.parseTrackSize(trackSize)
local maxTrack = Grid.parseTrackSize(maxStr:match("^%s*(.-)%s*$")) local maxTrack = Grid.parseTrackSize(maxStr:match("^%s*(.-)%s*$"))
return { type = "minmax", min = minTrack, max = maxTrack } return { type = "minmax", min = minTrack, max = maxTrack }
end end
-- Default to auto for unrecognized formats -- Default to auto for unrecognized formats
return { type = "auto" } return { type = "auto" }
end end
@@ -350,13 +371,13 @@ function Grid.parseTrackList(trackList)
end end
return result return result
end end
if type(trackList) ~= "string" then if type(trackList) ~= "string" then
return {} return {}
end end
local tracks = {} local tracks = {}
-- Handle repeat() function -- Handle repeat() function
local repeatMatch = trackList:match("^repeat%s*%(([^)]+)%)$") local repeatMatch = trackList:match("^repeat%s*%(([^)]+)%)$")
if repeatMatch then if repeatMatch then
@@ -372,12 +393,12 @@ function Grid.parseTrackList(trackList)
return tracks return tracks
end end
end end
-- Split by whitespace and parse each track -- Split by whitespace and parse each track
for trackStr in trackList:gmatch("%S+") do for trackStr in trackList:gmatch("%S+") do
table.insert(tracks, Grid.parseTrackSize(trackStr)) table.insert(tracks, Grid.parseTrackSize(trackStr))
end end
return tracks return tracks
end end
@@ -390,15 +411,15 @@ function Grid.resolveTrackSizes(tracks, availableSize, gap)
if #tracks == 0 then if #tracks == 0 then
return {} return {}
end end
-- Calculate total gap space -- Calculate total gap space
local totalGapSize = (#tracks - 1) * gap local totalGapSize = (#tracks - 1) * gap
local remainingSpace = availableSize - totalGapSize local remainingSpace = availableSize - totalGapSize
local resolvedSizes = {} local resolvedSizes = {}
local frTracks = {} local frTracks = {}
local frTotal = 0 local frTotal = 0
-- First pass: resolve fixed sizes and collect fr tracks -- First pass: resolve fixed sizes and collect fr tracks
for i, track in ipairs(tracks) do for i, track in ipairs(tracks) do
if track.type == "px" then if track.type == "px" then
@@ -431,7 +452,7 @@ function Grid.resolveTrackSizes(tracks, availableSize, gap)
end end
end end
end end
-- Second pass: distribute remaining space to fr tracks -- Second pass: distribute remaining space to fr tracks
if frTotal > 0 and remainingSpace > 0 then if frTotal > 0 and remainingSpace > 0 then
local frUnit = remainingSpace / frTotal local frUnit = remainingSpace / frTotal
@@ -449,7 +470,7 @@ function Grid.resolveTrackSizes(tracks, availableSize, gap)
resolvedSizes[i] = 0 resolvedSizes[i] = 0
end end
end end
-- Third pass: handle auto tracks (equal distribution of any remaining space) -- Third pass: handle auto tracks (equal distribution of any remaining space)
local autoTracks = {} local autoTracks = {}
for i, track in ipairs(tracks) do for i, track in ipairs(tracks) do
@@ -459,14 +480,14 @@ function Grid.resolveTrackSizes(tracks, availableSize, gap)
end end
end end
end end
if #autoTracks > 0 then if #autoTracks > 0 then
local autoSize = math.max(0, remainingSpace / #autoTracks) local autoSize = math.max(0, remainingSpace / #autoTracks)
for _, i in ipairs(autoTracks) do for _, i in ipairs(autoTracks) do
resolvedSizes[i] = autoSize resolvedSizes[i] = autoSize
end end
end end
return resolvedSizes return resolvedSizes
end end
@@ -477,33 +498,33 @@ function Grid.parsePlacement(placement)
if not placement then if not placement then
return { start = nil, end_ = nil, span = nil } return { start = nil, end_ = nil, span = nil }
end end
if type(placement) == "number" then if type(placement) == "number" then
return { start = placement, end_ = nil, span = nil } return { start = placement, end_ = nil, span = nil }
end end
if type(placement) ~= "string" then if type(placement) ~= "string" then
return { start = nil, end_ = nil, span = nil } return { start = nil, end_ = nil, span = nil }
end end
-- Handle "span N" format -- Handle "span N" format
local spanValue = placement:match("^span%s+(%d+)$") local spanValue = placement:match("^span%s+(%d+)$")
if spanValue then if spanValue then
return { start = nil, end_ = nil, span = tonumber(spanValue) } return { start = nil, end_ = nil, span = tonumber(spanValue) }
end end
-- Handle "start / end" format -- Handle "start / end" format
local startStr, endStr = placement:match("^(%d+)%s*/%s*(%d+)$") local startStr, endStr = placement:match("^(%d+)%s*/%s*(%d+)$")
if startStr and endStr then if startStr and endStr then
return { start = tonumber(startStr), end_ = tonumber(endStr), span = nil } return { start = tonumber(startStr), end_ = tonumber(endStr), span = nil }
end end
-- Handle single number -- Handle single number
local lineNum = tonumber(placement) local lineNum = tonumber(placement)
if lineNum then if lineNum then
return { start = lineNum, end_ = nil, span = nil } return { start = lineNum, end_ = nil, span = nil }
end end
return { start = nil, end_ = nil, span = nil } return { start = nil, end_ = nil, span = nil }
end end
@@ -517,9 +538,9 @@ end
function Grid.calculateItemPlacement(item, columnCount, rowCount, autoPlacementCursor, gridAutoFlow) function Grid.calculateItemPlacement(item, columnCount, rowCount, autoPlacementCursor, gridAutoFlow)
local columnPlacement = Grid.parsePlacement(item.gridColumn) local columnPlacement = Grid.parsePlacement(item.gridColumn)
local rowPlacement = Grid.parsePlacement(item.gridRow) local rowPlacement = Grid.parsePlacement(item.gridRow)
local columnStart, columnEnd, rowStart, rowEnd local columnStart, columnEnd, rowStart, rowEnd
-- Determine column placement -- Determine column placement
if columnPlacement.start and columnPlacement.end_ then if columnPlacement.start and columnPlacement.end_ then
columnStart = columnPlacement.start columnStart = columnPlacement.start
@@ -539,7 +560,7 @@ function Grid.calculateItemPlacement(item, columnCount, rowCount, autoPlacementC
columnStart = autoPlacementCursor.column columnStart = autoPlacementCursor.column
columnEnd = columnStart + 1 columnEnd = columnStart + 1
end end
-- Determine row placement -- Determine row placement
if rowPlacement.start and rowPlacement.end_ then if rowPlacement.start and rowPlacement.end_ then
rowStart = rowPlacement.start rowStart = rowPlacement.start
@@ -559,7 +580,7 @@ function Grid.calculateItemPlacement(item, columnCount, rowCount, autoPlacementC
rowStart = autoPlacementCursor.row rowStart = autoPlacementCursor.row
rowEnd = rowStart + 1 rowEnd = rowStart + 1
end end
return { return {
columnStart = columnStart, columnStart = columnStart,
columnEnd = columnEnd, columnEnd = columnEnd,
@@ -576,97 +597,99 @@ function Grid.layoutGridItems(element)
element.gridTemplateColumns = element.gridTemplateColumns or "1fr" element.gridTemplateColumns = element.gridTemplateColumns or "1fr"
element.gridTemplateRows = element.gridTemplateRows or "auto" element.gridTemplateRows = element.gridTemplateRows or "auto"
end end
-- Parse track definitions -- Parse track definitions
local columnTracks = Grid.parseTrackList(element.gridTemplateColumns or "1fr") local columnTracks = Grid.parseTrackList(element.gridTemplateColumns or "1fr")
local rowTracks = Grid.parseTrackList(element.gridTemplateRows or "auto") local rowTracks = Grid.parseTrackList(element.gridTemplateRows or "auto")
-- Calculate available space -- Calculate available space
local availableWidth = element.width - element.padding.left - element.padding.right local availableWidth = element.width - element.padding.left - element.padding.right
local availableHeight = element.height - element.padding.top - element.padding.bottom local availableHeight = element.height - element.padding.top - element.padding.bottom
-- Resolve track sizes -- Resolve track sizes
local columnGap = element.columnGap or 0 local columnGap = element.columnGap or 0
local rowGap = element.rowGap or 0 local rowGap = element.rowGap or 0
local columnSizes = Grid.resolveTrackSizes(columnTracks, availableWidth, columnGap) local columnSizes = Grid.resolveTrackSizes(columnTracks, availableWidth, columnGap)
local rowSizes = Grid.resolveTrackSizes(rowTracks, availableHeight, rowGap) local rowSizes = Grid.resolveTrackSizes(rowTracks, availableHeight, rowGap)
-- Calculate column and row positions -- Calculate column and row positions
local columnPositions = {} local columnPositions = {}
local rowPositions = {} local rowPositions = {}
local currentX = element.x + element.padding.left local currentX = element.x + element.padding.left
for i, size in ipairs(columnSizes) do for i, size in ipairs(columnSizes) do
columnPositions[i] = currentX columnPositions[i] = currentX
currentX = currentX + size + columnGap currentX = currentX + size + columnGap
end end
columnPositions[#columnSizes + 1] = currentX - columnGap -- End position columnPositions[#columnSizes + 1] = currentX - columnGap -- End position
local currentY = element.y + element.padding.top local currentY = element.y + element.padding.top
for i, size in ipairs(rowSizes) do for i, size in ipairs(rowSizes) do
rowPositions[i] = currentY rowPositions[i] = currentY
currentY = currentY + size + rowGap currentY = currentY + size + rowGap
end end
rowPositions[#rowSizes + 1] = currentY - rowGap -- End position rowPositions[#rowSizes + 1] = currentY - rowGap -- End position
-- Auto-placement cursor -- Auto-placement cursor
local autoPlacementCursor = { column = 1, row = 1 } local autoPlacementCursor = { column = 1, row = 1 }
local gridAutoFlow = element.gridAutoFlow or GridAutoFlow.ROW local gridAutoFlow = element.gridAutoFlow or GridAutoFlow.ROW
-- Place grid items -- Place grid items
for _, child in ipairs(element.children) do for _, child in ipairs(element.children) do
-- Skip explicitly absolute positioned children -- Skip explicitly absolute positioned children
if not (child.positioning == Positioning.ABSOLUTE and child._explicitlyAbsolute) then if not (child.positioning == Positioning.ABSOLUTE and child._explicitlyAbsolute) then
local placement = Grid.calculateItemPlacement( local placement = Grid.calculateItemPlacement(child, #columnSizes, #rowSizes, autoPlacementCursor, gridAutoFlow)
child,
#columnSizes,
#rowSizes,
autoPlacementCursor,
gridAutoFlow
)
-- Ensure placement is within bounds, expand grid if necessary -- Ensure placement is within bounds, expand grid if necessary
local columnStart = math.max(1, math.min(placement.columnStart, #columnSizes + 1)) local columnStart = math.max(1, math.min(placement.columnStart, #columnSizes + 1))
local columnEnd = math.max(columnStart + 1, math.min(placement.columnEnd, #columnSizes + 1)) local columnEnd = math.max(columnStart + 1, math.min(placement.columnEnd, #columnSizes + 1))
local rowStart = math.max(1, math.min(placement.rowStart, #rowSizes + 1)) local rowStart = math.max(1, math.min(placement.rowStart, #rowSizes + 1))
local rowEnd = math.max(rowStart + 1, math.min(placement.rowEnd, #rowSizes + 1)) local rowEnd = math.max(rowStart + 1, math.min(placement.rowEnd, #rowSizes + 1))
-- Calculate item position and size -- Calculate item position and size
local itemX = columnPositions[columnStart] or element.x local itemX = columnPositions[columnStart] or element.x
local itemY = rowPositions[rowStart] or element.y local itemY = rowPositions[rowStart] or element.y
local itemWidth = (columnPositions[columnEnd] or (element.x + element.width)) - itemX - columnGap local itemWidth = (columnPositions[columnEnd] or (element.x + element.width)) - itemX - columnGap
local itemHeight = (rowPositions[rowEnd] or (element.y + element.height)) - itemY - rowGap local itemHeight = (rowPositions[rowEnd] or (element.y + element.height)) - itemY - rowGap
-- Apply alignment within grid cell -- Apply alignment within grid cell
local effectiveJustifySelf = child.justifySelf or element.justifyItems or JustifyItems.STRETCH local effectiveJustifySelf = child.justifySelf or element.justifyItems or GridJustifyItems.STRETCH
local effectiveAlignSelf = child.alignSelf or element.alignItems or AlignItems.STRETCH local effectiveAlignSelf = child.alignSelf or element.alignItems or AlignItems.STRETCH
-- Handle justifySelf (horizontal alignment) -- Handle justifySelf (horizontal alignment)
if effectiveJustifySelf == JustifyItems.STRETCH or effectiveJustifySelf == "stretch" then if effectiveJustifySelf == GridJustifyItems.STRETCH or effectiveJustifySelf == "stretch" then
child.x = itemX + child.padding.left child.x = itemX + child.padding.left
child.width = itemWidth - child.padding.left - child.padding.right child.width = itemWidth - child.padding.left - child.padding.right
elseif effectiveJustifySelf == JustifyItems.START or effectiveJustifySelf == "start" or effectiveJustifySelf == "flex-start" then elseif effectiveJustifySelf == GridJustifyItems.START or effectiveJustifySelf == "start" or effectiveJustifySelf == "flex-start" then
child.x = itemX + child.padding.left child.x = itemX + child.padding.left
-- Keep child's natural width -- Keep child's natural width
elseif effectiveJustifySelf == JustifyItems.END or effectiveJustifySelf == "end" or effectiveJustifySelf == "flex-end" then elseif effectiveJustifySelf == GridJustifyItems.END or effectiveJustifySelf == "end" or effectiveJustifySelf == "flex-end" then
child.x = itemX + itemWidth - child.width - child.padding.right child.x = itemX + itemWidth - child.width - child.padding.right
elseif effectiveJustifySelf == JustifyItems.CENTER or effectiveJustifySelf == "center" then elseif effectiveJustifySelf == GridJustifyItems.CENTER or effectiveJustifySelf == "center" then
child.x = itemX + (itemWidth - child.width) / 2 child.x = itemX + (itemWidth - child.width) / 2
else else
-- Default to stretch -- Default to stretch
child.x = itemX + child.padding.left child.x = itemX + child.padding.left
child.width = itemWidth - child.padding.left - child.padding.right child.width = itemWidth - child.padding.left - child.padding.right
end end
-- Handle alignSelf (vertical alignment) -- Handle alignSelf (vertical alignment)
if effectiveAlignSelf == AlignItems.STRETCH or effectiveAlignSelf == "stretch" then if effectiveAlignSelf == AlignItems.STRETCH or effectiveAlignSelf == "stretch" then
child.y = itemY + child.padding.top child.y = itemY + child.padding.top
child.height = itemHeight - child.padding.top - child.padding.bottom child.height = itemHeight - child.padding.top - child.padding.bottom
elseif effectiveAlignSelf == AlignItems.FLEX_START or effectiveAlignSelf == "flex-start" or effectiveAlignSelf == "start" then elseif
effectiveAlignSelf == AlignItems.FLEX_START
or effectiveAlignSelf == "flex-start"
or effectiveAlignSelf == "start"
then
child.y = itemY + child.padding.top child.y = itemY + child.padding.top
-- Keep child's natural height -- Keep child's natural height
elseif effectiveAlignSelf == AlignItems.FLEX_END or effectiveAlignSelf == "flex-end" or effectiveAlignSelf == "end" then elseif
effectiveAlignSelf == AlignItems.FLEX_END
or effectiveAlignSelf == "flex-end"
or effectiveAlignSelf == "end"
then
child.y = itemY + itemHeight - child.height - child.padding.bottom child.y = itemY + itemHeight - child.height - child.padding.bottom
elseif effectiveAlignSelf == AlignItems.CENTER or effectiveAlignSelf == "center" then elseif effectiveAlignSelf == AlignItems.CENTER or effectiveAlignSelf == "center" then
child.y = itemY + (itemHeight - child.height) / 2 child.y = itemY + (itemHeight - child.height) / 2
@@ -675,7 +698,7 @@ function Grid.layoutGridItems(element)
child.y = itemY + child.padding.top child.y = itemY + child.padding.top
child.height = itemHeight - child.padding.top - child.padding.bottom child.height = itemHeight - child.padding.top - child.padding.bottom
end end
-- Update auto-placement cursor -- Update auto-placement cursor
if gridAutoFlow == GridAutoFlow.ROW or gridAutoFlow == "row" then if gridAutoFlow == GridAutoFlow.ROW or gridAutoFlow == "row" then
autoPlacementCursor.column = columnEnd autoPlacementCursor.column = columnEnd
@@ -690,7 +713,7 @@ function Grid.layoutGridItems(element)
autoPlacementCursor.column = autoPlacementCursor.column + 1 autoPlacementCursor.column = autoPlacementCursor.column + 1
end end
end end
-- Layout child's children if it has any -- Layout child's children if it has any
if #child.children > 0 then if #child.children > 0 then
child:layoutChildren() child:layoutChildren()
@@ -982,7 +1005,7 @@ end
---@field gridColumn string|number? -- Grid item column placement ---@field gridColumn string|number? -- Grid item column placement
---@field gridRow string|number? -- Grid item row placement ---@field gridRow string|number? -- Grid item row placement
---@field gridArea string? -- Grid item named area placement ---@field gridArea string? -- Grid item named area placement
---@field justifyItems JustifyItems? -- Default horizontal alignment for grid items ---@field justifyItems GridJustifyItems? -- Default horizontal alignment for grid items
---@field alignItems AlignItems? -- Default vertical alignment for grid items ---@field alignItems AlignItems? -- Default vertical alignment for grid items
local Element = {} local Element = {}
Element.__index = Element Element.__index = Element
@@ -1033,7 +1056,7 @@ Element.__index = Element
---@field gridColumn string|number? -- Grid item column placement (e.g., "1", "2 / 4", "span 2") ---@field gridColumn string|number? -- Grid item column placement (e.g., "1", "2 / 4", "span 2")
---@field gridRow string|number? -- Grid item row placement ---@field gridRow string|number? -- Grid item row placement
---@field gridArea string? -- Grid item named area placement ---@field gridArea string? -- Grid item named area placement
---@field justifyItems JustifyItems? -- Default horizontal alignment for grid items ---@field justifyItems GridJustifyItems? -- Default horizontal alignment for grid items
local ElementProps = {} local ElementProps = {}
---@param props ElementProps ---@param props ElementProps
@@ -1516,9 +1539,9 @@ function Element.new(props)
self.gridAutoRows = props.gridAutoRows or "auto" self.gridAutoRows = props.gridAutoRows or "auto"
self.justifyContent = props.justifyContent or GridJustifyContent.START self.justifyContent = props.justifyContent or GridJustifyContent.START
self.alignContent = props.alignContent or GridAlignContent.START self.alignContent = props.alignContent or GridAlignContent.START
self.justifyItems = props.justifyItems or JustifyItems.STRETCH self.justifyItems = props.justifyItems or GridJustifyItems.STRETCH
self.alignItems = props.alignItems or AlignItems.STRETCH self.alignItems = props.alignItems or AlignItems.STRETCH
-- Handle columnGap and rowGap -- Handle columnGap and rowGap
if props.columnGap then if props.columnGap then
if type(props.columnGap) == "string" then if type(props.columnGap) == "string" then
@@ -1530,7 +1553,7 @@ function Element.new(props)
else else
self.columnGap = 0 self.columnGap = 0
end end
if props.rowGap then if props.rowGap then
if type(props.rowGap) == "string" then if type(props.rowGap) == "string" then
local value, unit = Units.parse(props.rowGap) local value, unit = Units.parse(props.rowGap)
@@ -1580,11 +1603,15 @@ function Element:addChild(child)
table.insert(self.children, child) table.insert(self.children, child)
if self.autosizing.height then -- Only recalculate auto-sizing if the child participates in layout
self.height = self:calculateAutoHeight() -- (CSS: absolutely positioned children don't affect parent auto-sizing)
end if not child._explicitlyAbsolute then
if self.autosizing.width then if self.autosizing.height then
self.width = self:calculateAutoWidth() self.height = self:calculateAutoHeight()
end
if self.autosizing.width then
self.width = self:calculateAutoWidth()
end
end end
self:layoutChildren() self:layoutChildren()

View File

@@ -51,8 +51,8 @@ function TestAbsolutePositioningBasic:testDefaultAbsolutePositioning()
height = 100, height = 100,
}) })
-- Default should be relative positioning -- Default should be absolute positioning (RELATIVE not yet implemented)
luaunit.assertEquals(elem.positioning, Positioning.RELATIVE) luaunit.assertEquals(elem.positioning, Positioning.ABSOLUTE)
luaunit.assertEquals(elem.x, 50) luaunit.assertEquals(elem.x, 50)
luaunit.assertEquals(elem.y, 75) luaunit.assertEquals(elem.y, 75)
end end
@@ -288,7 +288,7 @@ function TestAbsolutePositioningBasic:testAbsoluteChildNoParentAutoSizeAffect()
positioning = Positioning.ABSOLUTE, positioning = Positioning.ABSOLUTE,
}) })
local originalParentWidtheight = parent.width local originalParentWidth = parent.width
local originalParentHeight = parent.height local originalParentHeight = parent.height
local child = Gui.new({ local child = Gui.new({
@@ -747,7 +747,7 @@ function TestAbsolutePositioningBasic:testAsymmetricAbsoluteTree()
}) })
-- Left branch: deep nesting -- Left branch: deep nesting
local leftBrancheight = Gui.new({ local leftBranch = Gui.new({
parent = root, parent = root,
id = "leftBranch", id = "leftBranch",
x = 100, x = 100,
@@ -792,7 +792,7 @@ function TestAbsolutePositioningBasic:testAsymmetricAbsoluteTree()
}) })
-- Right branch: wide shallow -- Right branch: wide shallow
local rightBrancheight = Gui.new({ local rightBranch = Gui.new({
parent = root, parent = root,
id = "rightBranch", id = "rightBranch",
x = 800, x = 800,

View File

@@ -868,7 +868,7 @@ function TestAbsolutePositioningChildLayout:testGridStructureAbsolutePositioning
-- Create grid cells -- Create grid cells
local cells = {} local cells = {}
for rowidth = 1, rows do for row = 1, rows do
cells[row] = {} cells[row] = {}
for col = 1, cols do for col = 1, cols do
local x = (col - 1) * (cellWidth + gap) local x = (col - 1) * (cellWidth + gap)
@@ -927,7 +927,7 @@ function TestAbsolutePositioningChildLayout:testGridStructureAbsolutePositioning
-- Verify grid structure -- Verify grid structure
luaunit.assertEquals(#grid.children, rows * cols) luaunit.assertEquals(#grid.children, rows * cols)
for rowidth = 1, rows do for row = 1, rows do
for col = 1, cols do for col = 1, cols do
local cell = cells[row][col] local cell = cells[row][col]
local expectedX = (col - 1) * (cellWidth + gap) local expectedX = (col - 1) * (cellWidth + gap)

View File

@@ -748,16 +748,18 @@ function TestAlignItems:testComplexCardLayoutMixedAlignItems()
luaunit.assertEquals(title.y, 23) -- Same center alignment luaunit.assertEquals(title.y, 23) -- Same center alignment
-- Verify actions buttons have FLEX_START alignment -- Verify actions buttons have FLEX_START alignment
luaunit.assertEquals(btn1.y, 10) -- Start of actions container -- actions is centered in header: header.y (10) + (header.height (50) - actions.height (30)) / 2 = 20
luaunit.assertEquals(btn2.y, 10) -- Same start position luaunit.assertEquals(btn1.y, 20) -- Start of actions container
luaunit.assertEquals(btn2.y, 20) -- Same start position
-- Verify content alignment (FLEX_END) -- Verify content alignment (FLEX_END)
luaunit.assertEquals(contentText.x, 60) -- 300 - 250 = 50, plus card.x = 10 + 50 = 60 luaunit.assertEquals(contentText.x, 60) -- 300 - 250 = 50, plus card.x = 10 + 50 = 60
luaunit.assertEquals(metadata.x, 130) -- 300 - 180 = 120, plus card.x = 10 + 120 = 130 luaunit.assertEquals(metadata.x, 130) -- 300 - 180 = 120, plus card.x = 10 + 120 = 130
-- Verify footer center alignment -- Verify footer center alignment
luaunit.assertEquals(timestamp.y, 175) -- Footer center: (30 - 16) / 2 = 7, plus footer.y = 168 + 7 = 175 -- footer.y = card.y (10) + header.height (50) + gap (10) + content.height (120) + gap (10) = 200
luaunit.assertEquals(status.y, 173) -- Footer center: (30 - 20) / 2 = 5, plus footer.y = 168 + 5 = 173 luaunit.assertEquals(timestamp.y, 207) -- Footer center: (30 - 16) / 2 = 7, plus footer.y = 200 + 7 = 207
luaunit.assertEquals(status.y, 205) -- Footer center: (30 - 20) / 2 = 5, plus footer.y = 200 + 5 = 205
end end
-- Test 17: Complex Media Object Pattern with Nested Alignments -- Test 17: Complex Media Object Pattern with Nested Alignments

View File

@@ -285,7 +285,7 @@ function TestGridLayout:test_justify_items_stretch()
positioning = enums.Positioning.GRID, positioning = enums.Positioning.GRID,
gridTemplateColumns = "100px 100px 100px", gridTemplateColumns = "100px 100px 100px",
gridTemplateRows = "100px", gridTemplateRows = "100px",
justifyItems = enums.JustifyItems.STRETCH, justifyItems = enums.GridJustifyItems.STRETCH,
columnGap = 0, columnGap = 0,
rowGap = 0, rowGap = 0,
padding = { horizontal = 0, vertical = 0 }, padding = { horizontal = 0, vertical = 0 },