Skip to content

Instantly share code, notes, and snippets.

@SPY
Created October 4, 2016 10:58
Show Gist options
  • Save SPY/0504974f61a56bde631b9cee1a94ac32 to your computer and use it in GitHub Desktop.
Save SPY/0504974f61a56bde631b9cee1a94ac32 to your computer and use it in GitHub Desktop.
export interface FlexChild {
flexBase: number,
flexGrow: number,
flexShrink: number,
maxWidth?: number,
minWidth?: number
}
function sumBy<T>(items: T[], getter: (item: T) => number): number {
return items.reduce((sum, item) => sum + getter(item), 0)
}
function bounded(desired: number, minWidth: number = 0, maxWidth?: number): number {
if (typeof maxWidth === 'number') {
return Math.min(maxWidth, Math.max(desired, minWidth))
}
else {
return Math.max(desired, minWidth)
}
}
export function flex(space: number, children: FlexChild[]): number[] {
const baseSpace = sumBy(children, child => child.flexBase)
const factor = baseSpace < space
? child => child.flexGrow
: child => child.flexShrink
const widths = children.map(child => child.flexBase)
let freeSpace = space - baseSpace
let resizeQueue = children.map((_, idx) => idx)
while (resizeQueue.length && freeSpace !== 0) {
const factors = sumBy(
resizeQueue.map(idx => children[idx]),
factor
) || 1
const forRemove = []
let unused = 0
resizeQueue.forEach(childIdx => {
const child = children[childIdx]
const current = widths[childIdx]
const possible = current + freeSpace * factor(child) / factors
const actual = bounded(possible, child.minWidth, child.maxWidth)
if (actual !== possible) {
unused += possible - actual
forRemove.push(childIdx)
}
widths[childIdx] = actual
})
resizeQueue = resizeQueue.filter(idx => forRemove.indexOf(idx) < 0)
freeSpace = unused
}
return widths
}
function assert(ok: boolean, message?: string) {
if (!ok) {
throw new Error(
'Assert failed. ' + (message || '')
)
}
}
function assertEq<T>(expected: T, actual: T, message?: string) {
assert(
expected === actual,
`Expected: ${expected}. Actual: ${actual}. ${message || ''}`
)
}
function assertArrayEq<T>(a: T[], b: T[], msg?: string) {
a.forEach((el, idx) => {
assertEq(b[idx], el, msg)
})
}
export function test() {
const children: FlexChild[] = [
{ flexBase: 50, flexGrow: 1, flexShrink: 1, minWidth: 40 },
{ flexBase: 70, flexGrow: 0, flexShrink: 2, minWidth: 70 },
{ flexBase: 100, flexGrow: 2, flexShrink: .5, maxWidth: 200 },
{ flexBase: 50, flexGrow: 1.5, flexShrink: 1, maxWidth: 70 },
{ flexBase: 30, flexGrow: .5, flexShrink: .5 }
]
assertEq(
300,
flex(300, children).reduce((sum, x) => sum + x),
'Sum of children widths should be equal sum of bases'
)
assertArrayEq(
[103, 70, 200, 70, 57],
flex(500, children).map(width => Math.round(width)),
'Children should correctly grow in large container'
)
assertArrayEq(
[40, 70, 78, 5, 8],
flex(200, children).map(width => Math.ceil(width)),
'Children should correctly shrink in small container'
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment