diff --git a/src/core/elementNode.ts b/src/core/elementNode.ts index 2a54d1b..e4f8505 100644 --- a/src/core/elementNode.ts +++ b/src/core/elementNode.ts @@ -1663,18 +1663,20 @@ export class ElementNode { flexDirection === 'row' || flexDirection === 'row-reverse'; flexFitsWidth = isFlexRow && node.flexBoundary !== 'fixed'; - if (isDev && flexFitsWidth) { - const needsWidthForJustify = [ - 'center', - 'flex-end', - 'space-between', - 'space-around', - 'space-evenly', - ].includes(node.justifyContent as string); - - if (needsWidthForJustify) { + // Every justify mode except flexStart positions children out of the + // container's free space. Shrinking to fit leaves none, so fall back + // to filling the parent unless the developer explicitly asked to + // contain — in which case honor it and warn about the contradiction. + if ( + flexFitsWidth && + node.justifyContent !== undefined && + node.justifyContent !== 'flexStart' + ) { + if (node.flexBoundary === undefined) { + flexFitsWidth = false; + } else if (isDev) { console.warn( - `justifyContent '${node.justifyContent}' requires an explicit width on the flex container; without one it shrinks to fit its children and has no free space to distribute: `, + `justifyContent '${node.justifyContent}' has no free space to distribute on a flexBoundary 'contain' container without an explicit width: `, this, ); } diff --git a/tests/flex-justify-width.test.tsx b/tests/flex-justify-width.test.tsx new file mode 100644 index 0000000..db916a1 --- /dev/null +++ b/tests/flex-justify-width.test.tsx @@ -0,0 +1,98 @@ +import * as v from 'vitest' +import * as lng from '@solidtv/solid' + +import {renderer, waitForUpdate} from './setup.js' + +const PARENT = 600 +const CHILD = 100 + +v.test('flex row without width fills parent when justifyContent needs free space', async () => { + let center!: lng.ElementNode + let centerOne!: lng.ElementNode + let between!: lng.ElementNode + let betweenTwo!: lng.ElementNode + + const dispose = renderer.render(() => ( + + + + + + + + + + + )) + + await waitForUpdate() + + // Container fills the parent instead of shrinking to its children... + v.assert.equal(center.width, PARENT) + // ...so there is free space to center into: (600 - 200) / 2 + v.assert.equal(centerOne.x, (PARENT - CHILD * 2) / 2) + + // spaceBetween pushes the last child to the far edge rather than to a + // negative offset against a zero-width container. + v.assert.equal(between.width, PARENT) + v.assert.equal(betweenTwo.x, PARENT - CHILD) + + dispose() +}) + +v.test('flex row without width still shrinks to fit for flexStart and no justifyContent', async () => { + let implicit!: lng.ElementNode + let start!: lng.ElementNode + + const dispose = renderer.render(() => ( + + + + + + + + + + + )) + + await waitForUpdate() + + v.assert.equal(implicit.width, CHILD * 2) + v.assert.equal(start.width, CHILD * 2) + + dispose() +}) + +v.test('an explicit flexBoundary contain wins over justifyContent and warns', async () => { + const warn = v.vi.spyOn(console, 'warn').mockImplementation(() => {}) + let contained!: lng.ElementNode + + const dispose = renderer.render(() => ( + + + + + + + )) + + await waitForUpdate() + + // The explicit contain is honored — the container is not widened to the parent. + v.assert.notEqual(contained.width, PARENT) + v.assert.isTrue( + warn.mock.calls.some((args) => String(args[0]).includes('justifyContent')), + 'expected a dev warning about the contradiction', + ) + + warn.mockRestore() + dispose() +})