Skip to content

Instantly share code, notes, and snippets.

@souporserious
Last active June 29, 2022 16:23
Show Gist options
  • Save souporserious/0432a255d00bf8225bf04e50cd21ff31 to your computer and use it in GitHub Desktop.
Save souporserious/0432a255d00bf8225bf04e50cd21ff31 to your computer and use it in GitHub Desktop.
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