Last active
June 29, 2022 16:23
-
-
Save souporserious/0432a255d00bf8225bf04e50cd21ff31 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type Node = { | |
type: 'grid' | 'row' | 'column' | 'space' | 'node' | |
parentType?: 'grid' | 'row' | 'column' | |
columns?: number | |
rows?: number | |
column?: number | |
columnOffset?: number | |
row?: number | |
rowOffset?: number | |
width: number | 'fill' | 'auto' | |
height: number | 'fill' | 'auto' | |
children?: Node[] | |
} | |
/** Compute width, height, column, and row values in a layout */ | |
function computeLayout(node: Node) { | |
const clonedNode = structuredClone(node) | |
clonedNode.column = 1 | |
clonedNode.row = 1 | |
clonedNode.width = node.columns | |
clonedNode.height = node.rows | |
const [row] = clonedNode.children | |
row.columns = clonedNode.columns | |
row.rows = clonedNode.rows | |
row.width = row.columns | |
row.height = row.rows | |
const [space1, column, space2] = row.children | |
if (column.width === 'auto') { | |
// this should be recursive and get ALL descendants | |
column.columns = column.children | |
.filter((node) => typeof node.width === 'number') | |
.reduce((total, node) => total + (node.width as number), 0) | |
} else if (column.width === 'fill') { | |
column.columns = row.columns | |
} else if (typeof column.width === 'number') { | |
column.columns = column.width | |
} | |
column.rows = row.rows | |
column.width = column.columns | |
column.height = column.rows | |
const remainingWidth = row.columns - column.children[1].width | |
const columnFillSize = remainingWidth / 2 | |
space1.width = columnFillSize | |
space1.height = row.rows | |
space2.width = columnFillSize | |
space2.height = row.rows | |
const [space3, node1, space4] = column.children | |
const remainingHeight = row.rows - node1.height | |
const rowFillSize = remainingHeight / 2 | |
space3.width = column.columns | |
space3.height = rowFillSize | |
space4.width = column.columns | |
space4.height = rowFillSize | |
return addOffsetsToNodes(clonedNode) | |
} | |
/** Computes "auto" values and adds intrinsic sizes to each layout node. */ | |
function computeAutoValues(node: Node) { | |
node.children.forEach((childNode) => { | |
if (isLayoutNode(childNode)) { | |
computeAutoValues(childNode) | |
} | |
}) | |
const widthFillCount = node.children.filter( | |
(predicateNode) => predicateNode.width === 'fill' | |
).length | |
const intrinsicWidth = node.children | |
.filter( | |
(childNode) => | |
typeof childNode.width === 'number' || childNode.intrinsicWidth | |
) | |
.reduce((total, childNode) => { | |
if (typeof childNode.width === 'number') { | |
return total + childNode.width | |
} | |
return total + childNode.intrinsicWidth | |
}, 0) | |
const maxIntrinsicWidth = Math.max( | |
...node.children | |
.filter( | |
(childNode) => | |
typeof childNode.width === 'number' || childNode.intrinsicWidth | |
) | |
.map( | |
(childNode) => childNode.intrinsicWidth ?? (childNode.width as number) | |
) | |
) | |
const heightFillCount = node.children.filter( | |
(predicateNode) => predicateNode.height === 'fill' | |
).length | |
const intrinsicHeight = node.children | |
.filter( | |
(childNode) => | |
typeof childNode.height === 'number' || childNode.intrinsicHeight | |
) | |
.reduce((total, childNode) => { | |
if (typeof childNode.height === 'number') { | |
return total + childNode.height | |
} | |
return total + childNode.intrinsicHeight | |
}, 0) | |
const maxIntrinsicHeight = Math.max( | |
...node.children | |
.filter( | |
(childNode) => | |
typeof childNode.height === 'number' || childNode.intrinsicHeight | |
) | |
.map( | |
(childNode) => childNode.intrinsicHeight ?? (childNode.height as number) | |
) | |
) | |
const isRow = node.type === 'row' | |
const isColumn = node.type === 'column' | |
node.intrinsicWidth = isRow ? intrinsicWidth : maxIntrinsicWidth | |
node.intrinsicHeight = isColumn ? intrinsicHeight : maxIntrinsicHeight | |
if (node.width === 'auto') { | |
if (widthFillCount > 0) { | |
node.width = 'fill' | |
} else { | |
node.width = node.children.reduce( | |
(total, childNode) => total + (childNode.width as number), | |
0 | |
) | |
} | |
} | |
if (node.height === 'auto') { | |
if (heightFillCount > 0) { | |
node.height = 'fill' | |
} else { | |
node.height = node.children.reduce( | |
(total, childNode) => total + (childNode.height as number), | |
0 | |
) | |
} | |
} | |
} | |
/** Adds column and row offsets to every node in a layout. */ | |
function addOffsetsToNodes(node: Node) { | |
node.children?.forEach((childNode, index) => { | |
const previousNode = node.children[index - 1] | |
/** Offset column and row children only, grid children do not affect one another. */ | |
if (previousNode) { | |
if (node.type === 'column') { | |
childNode.column = node.column | |
childNode.row = previousNode.row + (previousNode.height as number) | |
} else if (node.type === 'row') { | |
childNode.column = previousNode.column + (previousNode.width as number) | |
childNode.row = node.row | |
} | |
} else { | |
/** Measurements are relative so they start from the parent node. */ | |
childNode.column = node.column | |
childNode.row = node.row | |
} | |
addOffsetsToNodes(childNode) | |
}) | |
return node | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment