Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions src/core/elementNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
}
Expand Down
98 changes: 98 additions & 0 deletions tests/flex-justify-width.test.tsx
Original file line number Diff line number Diff line change
@@ -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(() => (
<view width={PARENT} height={PARENT}>
<view ref={center} display='flex' justifyContent='center' gap={0}>
<view ref={centerOne} width={CHILD} height={CHILD} />
<view width={CHILD} height={CHILD} />
</view>
<view ref={between} display='flex' justifyContent='spaceBetween' y={CHILD} gap={0}>
<view width={CHILD} height={CHILD} />
<view ref={betweenTwo} width={CHILD} height={CHILD} />
</view>
</view>
))

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(() => (
<view width={PARENT} height={PARENT}>
<view ref={implicit} display='flex' gap={0}>
<view width={CHILD} height={CHILD} />
<view width={CHILD} height={CHILD} />
</view>
<view ref={start} display='flex' justifyContent='flexStart' y={CHILD} gap={0}>
<view width={CHILD} height={CHILD} />
<view width={CHILD} height={CHILD} />
</view>
</view>
))

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(() => (
<view width={PARENT} height={PARENT}>
<view
ref={contained}
display='flex'
flexBoundary='contain'
justifyContent='center'
gap={0}
>
<view width={CHILD} height={CHILD} />
<view width={CHILD} height={CHILD} />
</view>
</view>
))

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()
})
Loading