Skip to content

Accept flex-flow's two values in either order - #198

Merged
TylerBrinks merged 1 commit into
TylerBrinks:masterfrom
jhaygood86:bugfix/flex-flow-either-order
Jul 23, 2026
Merged

Accept flex-flow's two values in either order#198
TylerBrinks merged 1 commit into
TylerBrinks:masterfrom
jhaygood86:bugfix/flex-flow-either-order

Conversation

@jhaygood86

Copy link
Copy Markdown
Contributor

Problem

flex-flow: row wrap;   /* accepted */
flex-flow: wrap row;   /* dropped */

flex-flow is <'flex-direction'> || <'flex-wrap'> (CSS Flexbox 1 §5.1). The double bar means the operands may appear in either order, but the two-value alternative is built with WithOrder, which is the OrderedOptionsConverter:

return directionConverter
      .Or(wrapConverter)
      .Or(WithOrder(directionConverter, wrapConverter));   // <-- requires direction first

So a reversed pair fails to convert and the declaration is discarded.

Fix

Use WithAny (the UnorderedOptionsConverter) for the pair.

|| still permits at most one occurrence of each operand, and that is preserved: row row, wrap wrap, row column, bogus wrap and wrap bogus all remain invalid. Reversed input normalizes to the canonical direction-then-wrap value, so flex-flow: wrap row yields row wrap.

I checked the other WithOrder call sites — flex, border-radius, font, background, border-image, cursor, counter-increment/-reset, transform-origin, the ratio and background-position/size compositions — and they all model genuinely ordered grammars. flex-flow was the only || implemented as ordered.

Known gap, left alone

Reconstructing the shorthand from its longhands still yields only the direction — flex-flow: row wrap read back via StyleDeclaration.FlexFlow gives row. That behaviour predates this change (it reproduces identically on master) and is out of scope here.

It isn't a one-line follow-up either: Or picks the first alternative that succeeds in Construct as well as Convert, so moving the pair ahead of the single-value alternatives fixes row wrap but regresses flex-flow: row to serialize as row initial, because the unset operand constructs the literal initial sentinel that ShorthandProperty.Export writes. Fixing it properly means suppressing that sentinel during re-serialization, which is a separate change.

Tests

14 theory cases in PropertyTests/FlexPropertyTests.cs: both orders for three direction/wrap pairs (asserting the normalized value and the two longhands), single values, and the invalid combinations.

3 fail on master. The full suite (1263 existing tests) stays green with no existing assertion modified, and all seven target frameworks build with no new warnings.

flex-flow is "<'flex-direction'> || <'flex-wrap'>". The double bar means
the two operands may appear in either order, but the two-value
alternative was built with WithOrder, which requires flex-direction
first, so a reversed pair was rejected outright:

  flex-flow: row wrap   /* accepted */
  flex-flow: wrap row   /* dropped, though equally valid */

Use WithAny for the pair. Duplicates and unknown keywords ("row row",
"wrap wrap", "bogus wrap") remain invalid, since "||" still permits at
most one occurrence of each operand.

Reconstructing the shorthand from its longhands still yields only the
direction ("flex-flow: row wrap" read back through StyleDeclaration
gives "row"). That predates this change and is left alone here: the Or
chain picks the first alternative that constructs, and moving the pair
ahead of the single-value alternatives makes an unset operand serialize
its "initial" sentinel instead ("row initial").
@jhaygood86
jhaygood86 marked this pull request as ready for review July 22, 2026 21:40
@TylerBrinks
TylerBrinks merged commit 3018b11 into TylerBrinks:master Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants