Created
October 4, 2016 10:58
-
-
Save SPY/0504974f61a56bde631b9cee1a94ac32 to your computer and use it in GitHub Desktop.
This file contains 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
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