Vixen
dd8b0a81
csharp
public static class ShorthandExpansion

Expands the shorthands ExCSS leaves alone because they contain a var().

No guide page documents this yet — the page shows what the code says about itself.

Remarks

The gap this closes, and how invisible it was. ExCSS expands a shorthand while parsing — border-color: #383c43 arrives as four border-*-color declarations — and everything downstream is written against that: the layout bridge reads only longhands and says so in its own remarks, and the draw list asks for border-top-color and border-top-left-radius by name. But a shorthand whose value mentions var() cannot be expanded at parse time, because what it expands to is not known until the custom property is resolved. ExCSS therefore hands it back whole, as a declaration named border-color — which nothing reads.

⚠ The symptom is silence. The declaration is present, the cascade carries it, the substitution resolves it, and it reaches a computed style under a name no consumer asks for. Every stylesheet in this repository writes border-color: var(--border), so every border in the framework was simply not drawn — no diagnostic, no missing value, nothing to notice except a control that looked flat.

Expanded at load rather than after substitution, which is where CSS puts it. The difference is the cascade: expanding afterwards would let border-top-color: red from a weaker rule beat border-color: var(--x) from a stronger one, because by then the winner has already been picked per property. Expanding here makes a var-bearing shorthand behave exactly as the same shorthand without one — which is the property worth having, and the one the tests assert against ExCSS's own output rather than against a table written here.

⚠ grid-column and grid-row are here for the opposite reason, and it is the reason NeedsExpanding exists. ExCSS has never heard of either property, so it hands them back whole whether or not they hold a var() — the var-only rule above would never fire and the shorthand would reach a computed style intact. That was not merely a missing expansion: it forced the layout bridge to apply the shorthand and then each longhand over it in an order fixed in code, so a grid-row-start from a theme sheet silently discarded a later grid-row: 1 / -1 from a utility class and the item was auto-placed into a real cell — the grid looked built rather than broken. Expanding at load gives the cascade two comparable declarations, and the later one wins, which is all "declaration order decides" ever needed.

⚠ inset is deliberately not here. ExCSS does not know that property either and passes it through whole whether or not it holds a var() — but the layout bridge reads the shorthand itself and no longhand overlaps it, so expanding it would be this file inventing a difference rather than removing one. Neither is flex: ExCSS does expand that one, and its one-value form means flex-grow for a number and flex-basis for a length, so which of those a var() holds is exactly what is not known yet.

⚠ place-self, place-items and place-content were the last three names on that list, and the reason they stayed on it was the grammar rather than the plumbing. ExCSS leaves all three whole, and every longhand they cover is read — LayoutStyleBuilder reads all six of align-/justify-items, -self and -content — so each of the three parsed, cascaded, resolved and then did nothing whatever, which is the border-colour silence one more time. ⚠ The naive fix is a whitespace split, and it is worse than the gap: each half of a place-* value is itself CSS Box Alignment §4's [ safe | unsafe ]? <position>, so place-content: safe center is one component meaning both axes and splitting it on the space emits align-content: safe and justify-content: center — two refused declarations where there should be two honoured ones. Alignment groups the tokens into components first, and it is the component count and not the token count that decides whether the value names one axis or two.

⚠ grid-area was the fourth name on that list until named areas landed, and it had to come off it in the same change. A named area is written grid-area: header far more often than it is written as four longhands, so leaving the shorthand inert would have shipped grid-template-areas with no ordinary way to use it — the "finished thing nothing calls" shape, one property wide.

Methods (3)

  • public static bool IsShorthand(string property)

    Whether this is a shorthand this can take apart.

  • public static bool NeedsExpanding(string property, string value)

    Whether this declaration has to be taken apart before the cascade sees it.

  • public static bool TryExpand(string property, string value, List<KeyValuePair<string, string>> into)

    Takes a shorthand apart into the longhands its consumers read.

Used by (2)

  • ShorthandExpansionTestsVixen.Ui.Styling.Tests
  • StyleSheetLoaderVixen.Ui.Styling