///|
/// Native span descriptor: use with table_spanning, not the flex table_row API.
pub struct TableSpanCell {
content : @minimoon.Node
colspan : Int
rowspan : Int
header : Bool
}
///|
pub fn[C : @minimoon.IsChildren] table_span_cell(
children : C,
colspan? : Int = 1,
rowspan? : Int = 1,
header? : Bool = false,
) -> TableSpanCell {
guard colspan > 0 && rowspan > 0 else {
abort("table spans must be positive")
}
{
content: @minimoon.fragment(children.to_nodes()),
colspan,
rowspan,
header,
}
}
///|
pub fn[C : @minimoon.IsChildren] table_span_head(
children : C,
colspan? : Int = 1,
rowspan? : Int = 1,
) -> TableSpanCell {
table_span_cell(children, colspan~, rowspan~, header=true)
}
///|
/// Equal-width columns and explicit row height provide real two-dimensional spans
/// without depending on HTML table or CSS grid behavior in Skyline. Empty rows
/// reserve space for cells carried from previous rows. Children must fit their
/// bounded cell; overflowing content is clipped. Invalid overlap/bounds abort.
pub fn table_spanning(
rows~ : Array[Array[TableSpanCell]],
columns~ : Int,
row_height? : Int = 48,
min_width? : Int = 0,
id? : String = "",
) -> @minimoon.Node {
guard columns > 0 &&
columns <= 100 &&
rows.length() <= 10000 &&
row_height > 0 &&
row_height <= 10000 &&
min_width >= 0 else {
abort("invalid native spanning table dimensions")
}
let occupied = Array::make(rows.length() * columns, false)
let row_nodes : Array[@minimoon.Node] = []
for row_index, cells in rows {
let nodes : Array[@minimoon.Node] = []
let mut column = 0
for cell in cells {
while column < columns && occupied[row_index * columns + column] {
column = column + 1
}
guard cell.colspan > 0 &&
cell.rowspan > 0 &&
cell.colspan <= columns - column &&
cell.rowspan <= rows.length() - row_index else {
abort("table span exceeds table bounds")
}
for y = row_index; y < row_index + cell.rowspan; y = y + 1 {
for x = column; x < column + cell.colspan; x = x + 1 {
guard !occupied[y * columns + x] else { abort("table spans overlap") }
occupied[y * columns + x] = true
}
}
let left = column.to_double() * 100.0 / columns.to_double()
let width = cell.colspan.to_double() * 100.0 / columns.to_double()
nodes.push(
ui_container(
if cell.header {
"table-head"
} else {
"table-cell"
},
style="position:absolute;box-sizing:border-box;overflow:hidden;min-width:0;top:0;left:" +
left.to_string() +
"%;width:" +
width.to_string() +
"%;height:" +
(cell.rowspan * row_height).to_string() +
"px;",
semantics=@minimoon.semantics(
role=if cell.header {
@minimoon.ColumnHeaderRole
} else {
@minimoon.CellRole
},
),
[cell.content],
),
)
column = column + cell.colspan
}
row_nodes.push(
ui_container(
"table-span-row",
style="position:relative;height:" + row_height.to_string() + "px;",
semantics=@minimoon.semantics(role=@minimoon.RowRole),
nodes,
),
)
}
@minimoon.scroll_view(
scroll_x=true,
class="mmui-table-scroll",
ui_container(
"table-spanning",
id~,
style="min-width:" + min_width.to_string() + "px;",
semantics=@minimoon.semantics(role=@minimoon.TableRole),
row_nodes,
),
)
}