@yahoo/uds-create-config 5.15.1 → 5.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -54,7 +54,7 @@ function classGrammar(config) {
54
54
  const list = byCss.get(property) ?? [];
55
55
  list.push(matchFor({
56
56
  path: entity.path,
57
- arbitrary: body.arbitrary === true,
57
+ arbitrary: Boolean(body.arbitrary),
58
58
  negates: body.negates === true
59
59
  }));
60
60
  byCss.set(property, list);
@@ -95,7 +95,37 @@ declare const ReactNativeSystemDefinition: ConfigClass<{
95
95
  values: _$zod.ZodOptional<_$zod.ZodArray<_$zod.ZodType<StylePropValue, unknown, _$zod_v4_core0.$ZodTypeInternals<StylePropValue, unknown>>>>;
96
96
  responsive: _$zod.ZodDefault<_$zod.ZodBoolean>;
97
97
  classPrefix: _$zod.ZodOptional<_$zod.ZodString>;
98
- arbitrary: _$zod.ZodDefault<_$zod.ZodBoolean>;
98
+ arbitrary: _$zod.ZodDefault<_$zod.ZodUnion<readonly [_$zod.ZodBoolean, _$zod.ZodEnum<{
99
+ ch: "ch";
100
+ dvh: "dvh";
101
+ dvw: "dvw";
102
+ em: "em";
103
+ ex: "ex";
104
+ lvh: "lvh";
105
+ lvw: "lvw";
106
+ px: "px";
107
+ rem: "rem";
108
+ svh: "svh";
109
+ svw: "svw";
110
+ vh: "vh";
111
+ vmax: "vmax";
112
+ vmin: "vmin";
113
+ vw: "vw";
114
+ "%": "%";
115
+ deg: "deg";
116
+ rad: "rad";
117
+ turn: "turn";
118
+ s: "s";
119
+ ms: "ms";
120
+ fr: "fr";
121
+ unitless: "unitless";
122
+ lh: "lh";
123
+ rlh: "rlh";
124
+ cqw: "cqw";
125
+ cqh: "cqh";
126
+ cqi: "cqi";
127
+ cqb: "cqb";
128
+ }>]>>;
99
129
  negates: _$zod.ZodDefault<_$zod.ZodBoolean>;
100
130
  selector: _$zod.ZodOptional<_$zod.ZodString>;
101
131
  alpha: _$zod.ZodOptional<_$zod.ZodString>;
@@ -59,6 +59,7 @@ import { addAndSubtractMigration } from "../migrations/20261005220312_add_and_su
59
59
  import { systemIncludeMigration } from "../migrations/20261005225949_system_include.js";
60
60
  import { motionVariantsAndLayoutMigration } from "../migrations/20261006125609_motion_variants_and_layout.js";
61
61
  import { systemScriptsMigration } from "../migrations/20261007182932_system_scripts.js";
62
+ import { styleNumbersNameNumericValuesMigration } from "../migrations/20261007210309_style_numbers_name_numeric_values.js";
62
63
  const System = defineConfig({
63
64
  name: WEB_CONFIG_NAME,
64
65
  description: "A design system: the values, primitives and compositions an app is built from. A ref is written `{ \"__ref\": \"<kind>:<path>\" }`, so a token is `{ \"__ref\": \"token:<group>/<name>\" }` and a token group is `{ \"__ref\": \"token:<group>\" }`. Build it in this order. Tokens first, in groups by purpose (colors, spacing, radii, type sizes), since everything else names them. Then style properties, each with `values` holding one ref to the token group whose members it accepts (`{ \"__ref\": \"token:color\" }`), so a prop takes token names rather than raw CSS; add a string keyword only for a value no token holds. A style rule's bag holds a prop's values (`\"padding\": \"4\"`, `\"bg\": \"brand\"`), never raw CSS. Then a few primitive components, one layer each, whose props are style properties (a box for layout and surface, a text for type, a pressable for interaction). Then every other component composed from those primitives: its layers name a primitive or an element, its anatomy places them (a node renders a layer, a tag or `Slot` where a slot prop's content lands; `children` is the edge list; `visible` is `{ \"$state\": \"<prop>\" }`; a prop shown in a nested layer is a `forward` route, and `$state` never sits in a node's props), and its style rules set style-property values per layer, keyed by prop values when a variant changes them. Modes (a dark mode is `{ type: \"selector\", value: \".dark\" }`) change token values through a token's `overrides`, so a component never restyles for a mode; modifiers (hover, focus, disabled) name a state, and a style rule applies under one as a `_hover` block inside a layer bag. Composites bundle style values that recur. Last, register canvas roles: the canvas draws with them, one entity per tool (`frame` for the layout primitive, `text` for the text primitive, `image`) whose entries name the components that tool inserts, and a tool whose role is unregistered is disabled. Give each component a `preview` entry under `*` so it shows content, and never write a CSS property name where a style-property name belongs.",
@@ -129,5 +130,6 @@ if (!registeredSchemaMigrations().has(20261005220312)) registerSchemaMigrations(
129
130
  if (!registeredSchemaMigrations().has(20261005225949)) registerSchemaMigrations(systemIncludeMigration);
130
131
  if (!registeredSchemaMigrations().has(20261006125609)) registerSchemaMigrations(motionVariantsAndLayoutMigration);
131
132
  if (!registeredSchemaMigrations().has(20261007182932)) registerSchemaMigrations(systemScriptsMigration);
133
+ if (!registeredSchemaMigrations().has(20261007210309)) registerSchemaMigrations(styleNumbersNameNumericValuesMigration);
132
134
  //#endregion
133
135
  export { System };
@@ -168,7 +168,37 @@ declare const NativeStyleProperty: EntityClass<z.ZodObject<Omit<{
168
168
  values: z.ZodOptional<z.ZodArray<z.ZodType<StylePropValue, unknown, z.core.$ZodTypeInternals<StylePropValue, unknown>>>>;
169
169
  responsive: z.ZodDefault<z.ZodBoolean>;
170
170
  classPrefix: z.ZodOptional<z.ZodString>;
171
- arbitrary: z.ZodDefault<z.ZodBoolean>;
171
+ arbitrary: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodEnum<{
172
+ ch: "ch";
173
+ dvh: "dvh";
174
+ dvw: "dvw";
175
+ em: "em";
176
+ ex: "ex";
177
+ lvh: "lvh";
178
+ lvw: "lvw";
179
+ px: "px";
180
+ rem: "rem";
181
+ svh: "svh";
182
+ svw: "svw";
183
+ vh: "vh";
184
+ vmax: "vmax";
185
+ vmin: "vmin";
186
+ vw: "vw";
187
+ "%": "%";
188
+ deg: "deg";
189
+ rad: "rad";
190
+ turn: "turn";
191
+ s: "s";
192
+ ms: "ms";
193
+ fr: "fr";
194
+ unitless: "unitless";
195
+ lh: "lh";
196
+ rlh: "rlh";
197
+ cqw: "cqw";
198
+ cqh: "cqh";
199
+ cqi: "cqi";
200
+ cqb: "cqb";
201
+ }>]>>;
172
202
  negates: z.ZodDefault<z.ZodBoolean>;
173
203
  selector: z.ZodOptional<z.ZodString>;
174
204
  alpha: z.ZodOptional<z.ZodString>;
@@ -13,7 +13,7 @@ import { Package } from "./Package.js";
13
13
  import { bindPreviewGlyphs, glyphLeafOf } from "./glyph-identity.js";
14
14
  import { Style, addBagKeyIssues, anatomyPropValue, bagNameEdges, bagRewriteName, bindBagTokens, collectBag, isPlainRecord, openBag, styleValue } from "./style-bag.js";
15
15
  import { storedPreview } from "../../framework/preview-store.js";
16
- import { glyphPropsOf, propOwner, propValueDomain, refListOf, resolveComponentProps, stylePropValueLeaves, validateComponentProps } from "../../framework/projections.js";
16
+ import { glyphPropsOf, propOwner, propValueDomain, refListOf, resolveComponentProps, stylePropNumberUnit, stylePropValueLeaves, validateComponentProps } from "../../framework/projections.js";
17
17
  import { carriesContent, intoName, isValueSlot, renderBoundProp, routedPropIn, routesContent } from "../../framework/prop-surface.js";
18
18
  import { inlineJsMotion } from "./Motion.js";
19
19
  import { slotAnatomyPropOf, slotTargetsOf, withoutTruthyTerm } from "../../framework/render-spec.js";
@@ -668,12 +668,15 @@ function styleBagValue({ config, declaration, name = "<prop>", glyphLibraries })
668
668
  gradient,
669
669
  shadowValue,
670
670
  refSchema
671
- ], { error: () => {
671
+ ], { error: (issue) => {
672
672
  const leaves = stylePropValueLeaves(config, property);
673
+ if (typeof issue.input === "number" && leaves.includes(String(issue.input))) return `\`${name}\`: \`${issue.input}\` names a value as a string, so write \`'${issue.input}'\`. A number names only a value the style property declares as a number.`;
673
674
  const shown = leaves.slice(0, 12).map((leaf) => `\`${leaf}\``);
674
675
  const more = leaves.length > 12 ? `, … (${leaves.length} in all)` : "";
675
- const arbitrary = (config.resolve("style-property", property)?.toJSON())?.arbitrary === true ? ", or a bracketed literal such as `[13px]`" : "";
676
- return `\`${name}\` takes one of ${shown.join(", ")}${more}${arbitrary}, or a token ref. A value the system has no token for is added to the style property's \`values\` first.`;
676
+ const arbitrary = (config.resolve("style-property", property)?.toJSON())?.arbitrary ? ", or a bracketed literal such as `[13px]`" : "";
677
+ const unit = stylePropNumberUnit(config, property);
678
+ const number = unit === void 0 ? "" : unit === "unitless" ? ", or a number" : `, or a number of \`${unit}\` such as \`600\``;
679
+ return `\`${name}\` takes one of ${shown.join(", ")}${more}${arbitrary}${number}, or a token ref. A value the system has no token for is added to the style property's \`values\` first.`;
677
680
  } });
678
681
  }
679
682
  /**
@@ -82,6 +82,10 @@ interface StylePropLeaf {
82
82
  readonly atRules?: Record<string, Record<string, string>>;
83
83
  /** The class the value is written as, when it names its own. */
84
84
  readonly className?: string;
85
+ /** The value was declared as a NUMBER — a numeric literal (`values: [1, 2]`) or alias
86
+ * (`{ alias: 3, … }`) — so it is written as one (`columns={3}`). A leaf without it is a string
87
+ * however it reads: a token named `4` is `spacing="4"`. */
88
+ readonly numeric?: true;
85
89
  }
86
90
  /** One entry a ref names: the leaf it is called and the qualified path it stores. */
87
91
  interface RefMember {
@@ -129,6 +133,13 @@ interface TokenVariableAsText {
129
133
  * follows it. A variable no token declares, such as a third-party library's, isn't one.
130
134
  */
131
135
  declare function tokenVariablesAsText(config: Config): TokenVariableAsText[];
136
+ /**
137
+ * The units a style property can read a bare number in (`arbitrary: 'px'`), and `unitless` for a
138
+ * property whose CSS takes a plain number (`line-height`, `opacity`). A closed list, so
139
+ * `arbitrary: 'pxx'` is refused rather than written into every declaration the prop makes.
140
+ */
141
+ declare const STYLE_PROP_NUMBER_UNITS: readonly ["unitless", "px", "rem", "em", "%", "ch", "ex", "lh", "rlh", "vw", "vh", "vmin", "vmax", "svw", "svh", "lvw", "lvh", "dvw", "dvh", "cqw", "cqh", "cqi", "cqb", "fr", "ms", "s", "deg", "rad", "turn"];
142
+ type StylePropNumberUnit = (typeof STYLE_PROP_NUMBER_UNITS)[number];
132
143
  /** StyleProperty (`flex/direction`) — a prop a component can expose, mapped to a CSS
133
144
  * property with an allowed value set. Grouping (`flex`) is emergent from the path. */
134
145
  declare const StylePropertyEntity: EntityClass<z.ZodObject<{
@@ -136,7 +147,37 @@ declare const StylePropertyEntity: EntityClass<z.ZodObject<{
136
147
  values: z.ZodOptional<z.ZodArray<z.ZodType<StylePropValue, unknown, z.core.$ZodTypeInternals<StylePropValue, unknown>>>>;
137
148
  responsive: z.ZodDefault<z.ZodBoolean>;
138
149
  classPrefix: z.ZodOptional<z.ZodString>;
139
- arbitrary: z.ZodDefault<z.ZodBoolean>;
150
+ arbitrary: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodEnum<{
151
+ ch: "ch";
152
+ dvh: "dvh";
153
+ dvw: "dvw";
154
+ em: "em";
155
+ ex: "ex";
156
+ lvh: "lvh";
157
+ lvw: "lvw";
158
+ px: "px";
159
+ rem: "rem";
160
+ svh: "svh";
161
+ svw: "svw";
162
+ vh: "vh";
163
+ vmax: "vmax";
164
+ vmin: "vmin";
165
+ vw: "vw";
166
+ "%": "%";
167
+ deg: "deg";
168
+ rad: "rad";
169
+ turn: "turn";
170
+ s: "s";
171
+ ms: "ms";
172
+ fr: "fr";
173
+ unitless: "unitless";
174
+ lh: "lh";
175
+ rlh: "rlh";
176
+ cqw: "cqw";
177
+ cqh: "cqh";
178
+ cqi: "cqi";
179
+ cqb: "cqb";
180
+ }>]>>;
140
181
  negates: z.ZodDefault<z.ZodBoolean>;
141
182
  selector: z.ZodOptional<z.ZodString>;
142
183
  alpha: z.ZodOptional<z.ZodString>;
@@ -168,4 +209,4 @@ declare function cssPropertyNames(styleProp: {
168
209
  readonly properties: readonly string[];
169
210
  }): string[];
170
211
  //#endregion
171
- export { RefMember, STYLE_PROP_VALUE_FORMS, STYLE_PROP_VALUE_LEAVES, StylePropLeaf, StylePropValue, StylePropValueCtx, StylePropValueKind, StyleProperty, StylePropertyClass, TokenVariableAsText, classifyStylePropValue, cssPropertyNames, leavesOfStylePropValue, stylePropValueFormOf, stylePropValueSchema, toCssPropertyName, tokenVariablesAsText };
212
+ export { RefMember, STYLE_PROP_NUMBER_UNITS, STYLE_PROP_VALUE_FORMS, STYLE_PROP_VALUE_LEAVES, StylePropLeaf, StylePropNumberUnit, StylePropValue, StylePropValueCtx, StylePropValueKind, StyleProperty, StylePropertyClass, TokenVariableAsText, classifyStylePropValue, cssPropertyNames, leavesOfStylePropValue, stylePropValueFormOf, stylePropValueSchema, toCssPropertyName, tokenVariablesAsText };
@@ -158,6 +158,10 @@ const STYLE_PROP_VALUE_FORMS = {
158
158
  })
159
159
  };
160
160
  const KINDS = Object.keys(STYLE_PROP_VALUE_FORMS);
161
+ /** `{ numeric: true }` for an alias declared as a number. */
162
+ function numericOf(alias) {
163
+ return typeof alias === "number" ? { numeric: true } : {};
164
+ }
161
165
  /** The value half of a flag as leaves carry it. */
162
166
  function flagValue(value, members) {
163
167
  if (typeof value === "string") return [{ value }];
@@ -180,7 +184,8 @@ const STYLE_PROP_VALUE_LEAVES = {
180
184
  }],
181
185
  number: (entry) => [{
182
186
  leaf: String(entry),
183
- value: String(entry)
187
+ value: String(entry),
188
+ numeric: true
184
189
  }],
185
190
  token: (entry, members) => members(entry).map(({ leaf, path }) => ({
186
191
  leaf,
@@ -202,18 +207,21 @@ const STYLE_PROP_VALUE_LEAVES = {
202
207
  aliasToken: (entry, members) => members(entry.value).map(({ path }) => ({
203
208
  leaf: String(entry.alias),
204
209
  value: path,
205
- ...classNameOf(entry)
210
+ ...classNameOf(entry),
211
+ ...numericOf(entry.alias)
206
212
  })),
207
213
  aliasLiteral: (entry) => [{
208
214
  leaf: String(entry.alias),
209
215
  value: entry.value,
210
- ...classNameOf(entry)
216
+ ...classNameOf(entry),
217
+ ...numericOf(entry.alias)
211
218
  }],
212
219
  aliasDeclarations: (entry) => [{
213
220
  leaf: String(entry.alias),
214
221
  value: "",
215
222
  ...splitDeclarations(entry.value),
216
- ...classNameOf(entry)
223
+ ...classNameOf(entry),
224
+ ...numericOf(entry.alias)
217
225
  }]
218
226
  };
219
227
  /** A bag's declarations as text, each ref as its marker. */
@@ -434,6 +442,66 @@ const tokenPathsByValue = createSliceMemo({
434
442
  return tokens;
435
443
  }
436
444
  });
445
+ /**
446
+ * The units a style property can read a bare number in (`arbitrary: 'px'`), and `unitless` for a
447
+ * property whose CSS takes a plain number (`line-height`, `opacity`). A closed list, so
448
+ * `arbitrary: 'pxx'` is refused rather than written into every declaration the prop makes.
449
+ */
450
+ const STYLE_PROP_NUMBER_UNITS = [
451
+ "unitless",
452
+ "px",
453
+ "rem",
454
+ "em",
455
+ "%",
456
+ "ch",
457
+ "ex",
458
+ "lh",
459
+ "rlh",
460
+ "vw",
461
+ "vh",
462
+ "vmin",
463
+ "vmax",
464
+ "svw",
465
+ "svh",
466
+ "lvw",
467
+ "lvh",
468
+ "dvw",
469
+ "dvh",
470
+ "cqw",
471
+ "cqh",
472
+ "cqi",
473
+ "cqb",
474
+ "fr",
475
+ "ms",
476
+ "s",
477
+ "deg",
478
+ "rad",
479
+ "turn"
480
+ ];
481
+ /**
482
+ * Why a prop can't read a bare number in a unit, or `undefined` when it can. A bare number is written
483
+ * as an inline declaration on each of the prop's `properties`, so a prop needs some, and one with a
484
+ * `selector` or `declarations` of its own would paint wrong that way. A field left out of `body` isn't
485
+ * judged, so an update is checked by only the fields it carries.
486
+ */
487
+ function numberUnitRefusal(body) {
488
+ if (body.properties !== void 0 && body.properties.length === 0) return "A prop that takes a number writes it to its `properties`, and this one sets none.";
489
+ if (body.selector || Object.keys(body.declarations ?? {}).length > 0) return "A number is written inline on the element, so a prop with a `selector` or its own `declarations` cannot take one. Use `arbitrary: true` for its off-scale values.";
490
+ }
491
+ /**
492
+ * A check refusing an `arbitrary` unit the rest of the body can't honour ({@link numberUnitRefusal}).
493
+ * An update carries only the fields it sets, so a field it leaves out is judged by the stored body,
494
+ * which the emitters guard themselves (`bodyNumberUnit`).
495
+ */
496
+ const numberFitsBody = (body) => (ctx) => {
497
+ if (typeof ctx.value !== "string") return;
498
+ const refusal = numberUnitRefusal(body ?? {});
499
+ if (refusal) ctx.issues.push({
500
+ code: "custom",
501
+ input: ctx.value,
502
+ message: refusal
503
+ });
504
+ };
437
505
  /** A check refusing a ref to a token or group nothing declares: it offers no values, and the prop
438
506
  * paints nothing. */
439
507
  const namesDeclaredTokens = (config) => (ctx) => {
@@ -493,7 +561,19 @@ const styleFields = ({ config, body, linked } = {}) => z.object({
493
561
  * to the prop's own path, which is the common case.
494
562
  */
495
563
  classPrefix: z.string().optional().describe("The class-name segment the prop contributes. Defaults to the prop's own path; `''` for self-describing values."),
496
- arbitrary: z.boolean().default(false).describe("Whether the prop also accepts a bracketed literal such as `[13px]`. Defaults to `false`."),
564
+ /**
565
+ * The prop's open side: what it takes beside the fixed list in `values`. `true` takes a bracketed
566
+ * literal (`[13px]`, `[#abc]`). A unit also takes a bare number, read in that unit: `'px'` makes
567
+ * `maxWidth={600}` paint `max-width: 600px` as `[600px]` would, and `'unitless'` writes the
568
+ * number as it is (`lineHeight={1.5}`). Any other unit is still a literal (`[40rem]`).
569
+ *
570
+ * One field, since a bare number is one more way to write an off-list value. Which unit a number
571
+ * means is the prop's API, so it is declared rather than read off the CSS grammar. On a prop with
572
+ * a unit the type decides: a number is the unit, and a value named by a number is the string
573
+ * (`spacing="4"` is the scale's step, `spacing={4}` is `4px`). A number the prop declares as a
574
+ * value (`values: [1, 2]`, `{ alias: 3, … }`) is still that value.
575
+ */
576
+ arbitrary: (config ? z.union([z.boolean(), z.enum(STYLE_PROP_NUMBER_UNITS)]).check(numberFitsBody(body)) : z.union([z.boolean(), z.enum(STYLE_PROP_NUMBER_UNITS)])).default(false).describe("What the prop takes beside its `values`: `true` takes a bracketed literal (`[13px]`); a unit also takes a bare number in it (`'px'` makes `600` paint `600px`, `'unitless'` writes `1.5` as it is). A number the prop declares as a value (`{ alias: 3, … }`) is still that value, and a token's name is a string: `spacing=\"4\"` is the step, `spacing={4}` a length. Defaults to `false`."),
497
577
  /**
498
578
  * The prop applies the NEGATION of its value — `offsetX="3"` paints `margin-inline: calc(var(…) *
499
579
  * -1)`.
@@ -578,4 +658,4 @@ function cssPropertyNames(styleProp) {
578
658
  return styleProp.properties.map(toCssPropertyName);
579
659
  }
580
660
  //#endregion
581
- export { STYLE_PROP_VALUE_FORMS, STYLE_PROP_VALUE_LEAVES, StyleProperty, classifyStylePropValue, cssPropertyNames, leavesOfStylePropValue, stylePropValueFormOf, stylePropValueSchema, toCssPropertyName, tokenVariablesAsText };
661
+ export { STYLE_PROP_NUMBER_UNITS, STYLE_PROP_VALUE_FORMS, STYLE_PROP_VALUE_LEAVES, StyleProperty, classifyStylePropValue, cssPropertyNames, leavesOfStylePropValue, numberUnitRefusal, stylePropValueFormOf, stylePropValueSchema, toCssPropertyName, tokenVariablesAsText };
@@ -73,7 +73,7 @@ interface RecordedSource {
73
73
  * The dense-counter `2` a short-lived scheme stamped before the chain existed reads as the mint
74
74
  * that replaced it (see `detectedWireVersion`).
75
75
  */
76
- declare const SERIALIZED_CONFIG_VERSION = 20261007182932;
76
+ declare const SERIALIZED_CONFIG_VERSION = 20261007210309;
77
77
  interface SerializedConfig {
78
78
  /** See {@link SERIALIZED_CONFIG_VERSION}. Always written; a stored artifact may lack it. */
79
79
  readonly version: number;
@@ -1245,6 +1245,17 @@ declare class Config {
1245
1245
  * the whole config; the index already knows the answer.
1246
1246
  */
1247
1247
  private cascadeRename;
1248
+ /**
1249
+ * A style property's unit changed: every stored bag that sets it a bare number is rewritten as what it
1250
+ * painted before (`pinBodyStyleNumbers`), so the change doesn't reread what's already written. `gap: 6`
1251
+ * named the token `'6'` until `gap` took `px`, and `maxWidth: 4` was `4px` until it lost its unit. The
1252
+ * rewritten form — the value's name, or its bracket literal — paints the same under either reading,
1253
+ * so a replay lands on the same bodies.
1254
+ *
1255
+ * Like {@link cascadeRename}, only the components the ref index says set the property are visited;
1256
+ * canvas roles hold props by name, so each is read.
1257
+ */
1258
+ private pinNumberReading;
1248
1259
  /**
1249
1260
  * Rename one key inside every record that is keyed by the renamed collection — a style rule's
1250
1261
  * `when`, which names the props it conditions on.
@@ -9,7 +9,7 @@ import { isComposedValue } from "../entities/system/composed-values.js";
9
9
  import { borrowedSlug, classifyOverlay } from "./source-integrity.js";
10
10
  import { kindBorrowable, titleCase } from "./registry.js";
11
11
  import { isGroupBody } from "./utils/group.js";
12
- import { authoredBag } from "./projections.js";
12
+ import { authoredBag, pinBodyStyleNumbers, stylePropNumberUnit } from "./projections.js";
13
13
  import { isAuthoringPhantom, phantomsIn } from "./utils/authoring-phantom.js";
14
14
  import { addressOf, editOp, inputOf, opVerb } from "./config-op.js";
15
15
  import { configSchemas } from "./config-ops.js";
@@ -1617,7 +1617,12 @@ var Config = class Config {
1617
1617
  }
1618
1618
  /** The next snapshot for one patch, validated. Pure in `snapshot`; reads metadata off `this`. */
1619
1619
  computeApply(snapshot, incoming, mode = "write", checked = true) {
1620
- const next = this.computeApplyPatch(snapshot, incoming, mode);
1620
+ const unitChange = incoming.kind === "style-property" && incoming.operation === "update" ? {
1621
+ path: incoming.path,
1622
+ was: stylePropNumberUnit(this.viewOf(snapshot), incoming.path)
1623
+ } : void 0;
1624
+ let next = this.computeApplyPatch(snapshot, incoming, mode);
1625
+ if (unitChange) next = this.pinNumberReading(next, unitChange.path, unitChange.was);
1621
1626
  const view = this.viewOf(next);
1622
1627
  if (checked) view.assertInvariants(incoming.kind);
1623
1628
  else view.assertUniqueLeaves(incoming.kind);
@@ -2272,6 +2277,44 @@ var Config = class Config {
2272
2277
  return snapshot.withChanges(changes);
2273
2278
  }
2274
2279
  /**
2280
+ * A style property's unit changed: every stored bag that sets it a bare number is rewritten as what it
2281
+ * painted before (`pinBodyStyleNumbers`), so the change doesn't reread what's already written. `gap: 6`
2282
+ * named the token `'6'` until `gap` took `px`, and `maxWidth: 4` was `4px` until it lost its unit. The
2283
+ * rewritten form — the value's name, or its bracket literal — paints the same under either reading,
2284
+ * so a replay lands on the same bodies.
2285
+ *
2286
+ * Like {@link cascadeRename}, only the components the ref index says set the property are visited;
2287
+ * canvas roles hold props by name, so each is read.
2288
+ */
2289
+ pinNumberReading(after, path, was) {
2290
+ const view = this.viewOf(after);
2291
+ if (was === stylePropNumberUnit(view, path)) return after;
2292
+ const reading = new Map([[path, was]]);
2293
+ const changes = [];
2294
+ const visit = (kind, itemPath) => {
2295
+ const entry = this.entityOf(kind);
2296
+ const target = after.slice(kind).items.get(itemPath);
2297
+ if (!entry || !target) return;
2298
+ const body = target.toJSON();
2299
+ const pinned = pinBodyStyleNumbers(view, kind, body, reading, itemPath);
2300
+ if (pinned === body) return;
2301
+ let keys = isExtendedRef(pinned) ? ["__ref", "extend"] : entry.fieldKeys;
2302
+ if (isMintedRow(pinned)) keys = [...keys, MINTED];
2303
+ changes.push({
2304
+ kind,
2305
+ target: "item",
2306
+ path: itemPath,
2307
+ value: new Entity(itemPath, pinned, keys)
2308
+ });
2309
+ };
2310
+ for (const dependent of this.referrersOf(after, "style-property", path)) {
2311
+ const { kind, qualifiedPath } = splitRef(dependent);
2312
+ if (kind === "component") visit(kind, qualifiedPath);
2313
+ }
2314
+ if (this.entityOf("canvas-role")) for (const rolePath of after.slice("canvas-role").items.keys()) visit("canvas-role", rolePath);
2315
+ return changes.length > 0 ? after.withChanges(changes) : after;
2316
+ }
2317
+ /**
2275
2318
  * Rename one key inside every record that is keyed by the renamed collection — a style rule's
2276
2319
  * `when`, which names the props it conditions on.
2277
2320
  *
@@ -5382,7 +5425,7 @@ var Config = class Config {
5382
5425
  * links from the provided sources. A declared `linkedKind` with no provided source throws. */
5383
5426
  hydrateFrom(stored, options) {
5384
5427
  const claimed = schemaVersionOf(stored);
5385
- if (claimed > 20261007182932) throw new SchemaVersionTooNew(claimed);
5428
+ if (claimed > 20261007210309) throw new SchemaVersionTooNew(claimed);
5386
5429
  const wire = upgradeSerializedConfig(stored);
5387
5430
  if (!Array.isArray(wire.ownedKinds) || typeof wire.items !== "object" || wire.items === null) throw new ConfigFormatError(`Config "${stored.name ?? "system-config"}" cannot be hydrated: this is not a serialized config-v2 config (no "ownedKinds"/"items"). A pre-cutover build artifact converts through the registered cutover migration — import the config type's module (e.g. \`configs/system\`) before hydrating, or port the repo once with \`uds migrate\`.`);
5388
5431
  const leftover = wire.options;
@@ -281,6 +281,16 @@ declare function arbitraryClassName({
281
281
  readonly path: string; /** The authored value, brackets included. */
282
282
  readonly value: string;
283
283
  }): string;
284
+ /**
285
+ * The bracket literal an off-scale value is written as where it needs a class, not an inline style:
286
+ * under a modifier, whose condition an inline style can't hold. A bracket literal on a prop that takes
287
+ * one is itself, and a bare number in the prop's unit is its literal (`600` with `arbitrary: 'px'` is
288
+ * `[600px]`). `undefined` for a value a declared class paints, or one nothing does.
289
+ *
290
+ * The canvas split, the build's class extractor and the emitted runtime all name the class this gives,
291
+ * so the element wears the class the sheet has a rule for.
292
+ */
293
+ declare function stylePropOffScaleLiteral(config: Config, path: string, value: unknown): string | undefined;
284
294
  /** What a prop bag amounts to once styling is taken out of it. */
285
295
  interface SplitStyleProps {
286
296
  /** The classes the bag's style props produce, space-joined. */
@@ -304,10 +314,14 @@ interface SplitStyleProps {
304
314
  declare function splitStyleProps({
305
315
  config,
306
316
  bag,
307
- through
317
+ through,
318
+ underModifier
308
319
  }: {
309
320
  readonly config: Config;
310
321
  readonly bag: Record<string, unknown>;
322
+ /** The bag is a modifier's block. An inline style can't hold its condition, so an off-scale value
323
+ * is its arbitrary class (see {@link stylePropOffScaleLiteral}) rather than a declaration. */
324
+ readonly underModifier?: boolean;
311
325
  /** The definitions this bag's keys resolve against, in precedence order — see
312
326
  * {@link stylePropertyPathFor}. `<VStack gap="4">` wears `uds-gap-y-4`. Empty for a bag with no
313
327
  * owning component, where a key names its own property. */
@@ -347,4 +361,4 @@ declare function modifierUtilitiesUsed(config: Config): Set<string>;
347
361
  */
348
362
  declare function splitVariants(cls: string): string[];
349
363
  //#endregion
350
- export { ComponentRuleClass, PickedComposite, SplitStyleProps, arbitraryClassName, arbitraryNeedsClass, classLeaf, classSelector, componentClassBase, componentCompositeClasses, componentLayerClass, componentMotionClasses, componentPropClasses, componentPropComposites, componentRuleClasses, forceModeAttribute, forceModeAttributeOf, forceModeProp, forceModePropGroup, forceModePropValue, forceModeProps, forceStateAttribute, inlineRuleComposites, isForceModeProp, kebabComponent, layerJsMotionPresets, layerRoutedProps, modifierUtilitiesUsed, motionClassName, ruleCondition, splitStyleProps, splitVariants, stylePropClassBase, stylePropClassName, stylePropClasses, stylePropOwnClasses, styleRuleMotionClasses };
364
+ export { ComponentRuleClass, PickedComposite, SplitStyleProps, arbitraryClassName, arbitraryNeedsClass, classLeaf, classSelector, componentClassBase, componentCompositeClasses, componentLayerClass, componentMotionClasses, componentPropClasses, componentPropComposites, componentRuleClasses, forceModeAttribute, forceModeAttributeOf, forceModeProp, forceModePropGroup, forceModePropValue, forceModeProps, forceStateAttribute, inlineRuleComposites, isForceModeProp, kebabComponent, layerJsMotionPresets, layerRoutedProps, modifierUtilitiesUsed, motionClassName, ruleCondition, splitStyleProps, splitVariants, stylePropClassBase, stylePropClassName, stylePropClasses, stylePropOffScaleLiteral, stylePropOwnClasses, styleRuleMotionClasses };
@@ -3,7 +3,7 @@ import { forwardsOf, rootLayerOf } from "../entities/system/element.js";
3
3
  import { createSliceMemo } from "./memo.js";
4
4
  import { classPrefixOf, prefixed, varPrefixOf } from "../entities/system/Settings.js";
5
5
  import { storedPreview } from "./preview-store.js";
6
- import { authoredValue, className, isCustomPropertyKey, negatedCssValue, renderStyleValue, resolveComponentProps, stylePropEntries, stylePropLeafForToken, stylePropValueLeaves, stylePropertyPathFor } from "./projections.js";
6
+ import { authoredValue, className, isCustomPropertyKey, negatedCssValue, renderStyleValue, resolveComponentProps, stylePropEntries, stylePropLeafForToken, stylePropNumberLeaf, stylePropNumberUnit, stylePropValueLeaves, stylePropertyPathFor } from "./projections.js";
7
7
  import { inlineMotionId, isInlineMotion, jsMotionBody } from "../entities/system/motion-variants.js";
8
8
  import { ruleApplies } from "./layer-styles.js";
9
9
  //#region src/framework/class-names.ts
@@ -725,6 +725,25 @@ function arbitraryClassName({ config, path, value }) {
725
725
  return `${stylePropClassBase(config, path)}-[${inner}]`;
726
726
  }
727
727
  /**
728
+ * The bracket literal an off-scale value is written as where it needs a class, not an inline style:
729
+ * under a modifier, whose condition an inline style can't hold. A bracket literal on a prop that takes
730
+ * one is itself, and a bare number in the prop's unit is its literal (`600` with `arbitrary: 'px'` is
731
+ * `[600px]`). `undefined` for a value a declared class paints, or one nothing does.
732
+ *
733
+ * The canvas split, the build's class extractor and the emitted runtime all name the class this gives,
734
+ * so the element wears the class the sheet has a rule for.
735
+ */
736
+ function stylePropOffScaleLiteral(config, path, value) {
737
+ const { arbitrary } = config.resolve("style-property", path)?.toJSON() ?? {};
738
+ if (!arbitrary) return void 0;
739
+ if (typeof value === "string") return offScale(value) === void 0 ? void 0 : value;
740
+ if (typeof value !== "number" || !Number.isFinite(value)) return void 0;
741
+ if (stylePropNumberLeaf(config, path, value) !== void 0) return void 0;
742
+ const unit = stylePropNumberUnit(config, path);
743
+ if (unit === void 0) return void 0;
744
+ return `[${value}${unit === "unitless" ? "" : unit}]`;
745
+ }
746
+ /**
728
747
  * Split a prop bag into a class string and the props that aren't styling.
729
748
  *
730
749
  * This is the same dispatch the emitted `_style-props` runtime performs, over a live config instead
@@ -737,19 +756,20 @@ function arbitraryClassName({ config, path, value }) {
737
756
  * ({@link arbitraryNeedsClass}); an explicit `style` the caller passed still wins, since that's the
738
757
  * escape hatch of last resort.
739
758
  */
740
- function splitStyleProps({ config, bag, through }) {
759
+ function splitStyleProps({ config, bag, through, underModifier = false }) {
741
760
  const classes = [];
742
761
  const rest = {};
743
762
  const style = {};
744
763
  const modifiers = new Map(config.list("modifier").map((modifier) => [`_${modifier.leaf}`, modifier.leaf]));
745
- for (const [key, value] of Object.entries(bag)) {
764
+ for (const [key, authored] of Object.entries(bag)) {
746
765
  const modifier = modifiers.get(key);
747
766
  if (modifier) {
748
- if (value && typeof value === "object" && !Array.isArray(value)) {
767
+ if (authored && typeof authored === "object" && !Array.isArray(authored)) {
749
768
  const inner = splitStyleProps({
750
769
  config,
751
- bag: value,
752
- through
770
+ bag: authored,
771
+ through,
772
+ underModifier: true
753
773
  });
754
774
  for (const cls of inner.className.split(" ").filter(Boolean)) classes.push(`${modifier}:${cls}`);
755
775
  }
@@ -763,11 +783,27 @@ function splitStyleProps({ config, bag, through }) {
763
783
  }) ?? key;
764
784
  const prop = config.resolve("style-property", name);
765
785
  if (!prop) {
766
- rest[key] = value;
786
+ rest[key] = authored;
767
787
  continue;
768
788
  }
769
- if (value === void 0 || value === null) continue;
789
+ if (authored === void 0 || authored === null) continue;
770
790
  const { arbitrary, properties = [], negates } = prop.toJSON();
791
+ const value = typeof authored === "number" ? stylePropOffScaleLiteral(config, name, authored) ?? authored : authored;
792
+ if (underModifier && typeof value === "string" && arbitrary) {
793
+ if (offScale(value) !== void 0) {
794
+ classes.push(arbitraryClassName({
795
+ config,
796
+ path: name,
797
+ value
798
+ }));
799
+ continue;
800
+ }
801
+ }
802
+ if (typeof value === "number") {
803
+ const leaf = stylePropNumberLeaf(config, name, value);
804
+ if (leaf !== void 0) classes.push(stylePropClassName(config, name, leaf));
805
+ continue;
806
+ }
771
807
  const off = arbitrary ? offScale(value) : void 0;
772
808
  if (off !== void 0 && arbitraryNeedsClass(config, name)) {
773
809
  classes.push(arbitraryClassName({
@@ -970,4 +1006,4 @@ function splitVariants(cls) {
970
1006
  return segments;
971
1007
  }
972
1008
  //#endregion
973
- export { arbitraryClassName, arbitraryNeedsClass, classLeaf, classSelector, componentClassBase, componentCompositeClasses, componentLayerClass, componentMotionClasses, componentPropClasses, componentPropComposites, componentRuleClasses, forceModeAttribute, forceModeAttributeOf, forceModeProp, forceModePropGroup, forceModePropValue, forceModeProps, forceStateAttribute, inlineRuleComposites, isForceModeProp, kebabComponent, layerJsMotionPresets, layerRoutedProps, modifierUtilitiesUsed, motionClassName, ruleCondition, splitStyleProps, splitVariants, stylePropClassBase, stylePropClassName, stylePropClasses, stylePropOwnClasses, styleRuleMotionClasses };
1009
+ export { arbitraryClassName, arbitraryNeedsClass, classLeaf, classSelector, componentClassBase, componentCompositeClasses, componentLayerClass, componentMotionClasses, componentPropClasses, componentPropComposites, componentRuleClasses, forceModeAttribute, forceModeAttributeOf, forceModeProp, forceModePropGroup, forceModePropValue, forceModeProps, forceStateAttribute, inlineRuleComposites, isForceModeProp, kebabComponent, layerJsMotionPresets, layerRoutedProps, modifierUtilitiesUsed, motionClassName, ruleCondition, splitStyleProps, splitVariants, stylePropClassBase, stylePropClassName, stylePropClasses, stylePropOffScaleLiteral, stylePropOwnClasses, styleRuleMotionClasses };