@yahoo/uds-create-config 4.58.0 → 5.1.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.
@@ -105,14 +105,14 @@ declare const ReactNativeSystemDefinition: ConfigClass<{
105
105
  properties: _$zod.ZodArray<_$zod.ZodPipe<_$zod.ZodPipe<_$zod.ZodString, _$zod.ZodTransform<string, string>>, _$zod.ZodEnum<{
106
106
  transform: "transform";
107
107
  filter: "filter";
108
- left: "left";
109
- right: "right";
110
108
  color: "color";
111
109
  inset: "inset";
112
110
  position: "position";
113
111
  end: "end";
114
112
  start: "start";
115
113
  bottom: "bottom";
114
+ left: "left";
115
+ right: "right";
116
116
  top: "top";
117
117
  flex: "flex";
118
118
  cursor: "cursor";
@@ -55,6 +55,7 @@ import { motionExitAndElementDuplicateMigration } from "../migrations/2026100113
55
55
  import { layerStyleAndForwardOpsMigration } from "../migrations/20261001131000_layer_style_and_forward_ops.js";
56
56
  import { oklchAndEmitFieldsMigration } from "../migrations/20261001162046_oklch_and_emit_fields.js";
57
57
  import { globalStyleOnMigration } from "../migrations/20261005212405_global_style_on.js";
58
+ import { addAndSubtractMigration } from "../migrations/20261005220312_add_and_subtract.js";
58
59
  const System = defineConfig({
59
60
  name: WEB_CONFIG_NAME,
60
61
  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.",
@@ -121,5 +122,6 @@ if (!registeredSchemaMigrations().has(20261001130958)) registerSchemaMigrations(
121
122
  if (!registeredSchemaMigrations().has(20261001131e3)) registerSchemaMigrations(layerStyleAndForwardOpsMigration);
122
123
  if (!registeredSchemaMigrations().has(20261001162046)) registerSchemaMigrations(oklchAndEmitFieldsMigration);
123
124
  if (!registeredSchemaMigrations().has(20261005212405)) registerSchemaMigrations(globalStyleOnMigration);
125
+ if (!registeredSchemaMigrations().has(20261005220312)) registerSchemaMigrations(addAndSubtractMigration);
124
126
  //#endregion
125
127
  export { System };
@@ -1,4 +1,4 @@
1
- import { evaluateCalc, isDivide, isMultiply } from "../entities/system/calc.js";
1
+ import { evaluateCalc, isCalc } from "../entities/system/calc.js";
2
2
  import { composedValueFormOf } from "../entities/system/composed-values.js";
3
3
  //#region src/css/value-type.ts
4
4
  /**
@@ -89,7 +89,7 @@ function startsWithAny(value, heads) {
89
89
  function sniffValueType(value) {
90
90
  const formType = composedValueFormOf(value)?.valueType;
91
91
  if (formType) return formType;
92
- if (isMultiply(value) || isDivide(value)) {
92
+ if (isCalc(value)) {
93
93
  const literal = evaluateCalc(value, (part) => typeof part === "object" ? void 0 : part);
94
94
  return literal === void 0 ? void 0 : sniffValueType(String(literal));
95
95
  }
@@ -8,14 +8,14 @@ import { z } from "zod";
8
8
  declare const rnStyleKey: z.ZodEnum<{
9
9
  transform: "transform";
10
10
  filter: "filter";
11
- left: "left";
12
- right: "right";
13
11
  color: "color";
14
12
  inset: "inset";
15
13
  position: "position";
16
14
  end: "end";
17
15
  start: "start";
18
16
  bottom: "bottom";
17
+ left: "left";
18
+ right: "right";
19
19
  top: "top";
20
20
  flex: "flex";
21
21
  cursor: "cursor";
@@ -180,14 +180,14 @@ declare const NativeStyleProperty: EntityClass<z.ZodObject<Omit<{
180
180
  properties: z.ZodArray<z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>, z.ZodEnum<{
181
181
  transform: "transform";
182
182
  filter: "filter";
183
- left: "left";
184
- right: "right";
185
183
  color: "color";
186
184
  inset: "inset";
187
185
  position: "position";
188
186
  end: "end";
189
187
  start: "start";
190
188
  bottom: "bottom";
189
+ left: "left";
190
+ right: "right";
191
191
  top: "top";
192
192
  flex: "flex";
193
193
  cursor: "cursor";
@@ -1,4 +1,4 @@
1
- import { isRef, pathOf, ref, sourceOf } from "../../framework/utils/refs.js";
1
+ import { isRef, pathOf, ref, refKey, sourceOf } from "../../framework/utils/refs.js";
2
2
  import { composedValueSchemas } from "./composed-values.js";
3
3
  import { defineEntity } from "../../framework/defineEntity.js";
4
4
  import { defineSubEntity } from "../../framework/defineSubEntity.js";
@@ -6,7 +6,7 @@ import { Font } from "./Font.js";
6
6
  import { Modifier } from "./Modifier.js";
7
7
  import { WEB_CONFIG_NAME } from "../../configs/platform.js";
8
8
  import { authoringPhantom } from "../../framework/utils/authoring-phantom.js";
9
- import { TOKEN_READERS, spellTokenRead } from "./token-values.js";
9
+ import { spellTokenRead } from "./token-values.js";
10
10
  import { prepareLinkedTokenBody } from "./linked-token-values.js";
11
11
  import { z } from "zod";
12
12
  //#region src/entities/system/Token.ts
@@ -156,13 +156,26 @@ const TokenEntity = defineEntity({
156
156
  const baseTokenRef = TokenEntity.ref.bind(TokenEntity);
157
157
  function tokenRef(path) {
158
158
  const value = baseTokenRef(path);
159
- for (const reader of TOKEN_READERS) Object.defineProperty(value, reader, {
159
+ const phantom = (reader) => () => authoringPhantom({ spelled: spellTokenRead({
160
+ path,
161
+ reader
162
+ }) });
163
+ for (const reader of [
164
+ "value",
165
+ "px",
166
+ "ms"
167
+ ]) Object.defineProperty(value, reader, {
168
+ enumerable: false,
169
+ configurable: false,
170
+ value: phantom(reader)
171
+ });
172
+ Object.defineProperty(value, "cssVariable", {
160
173
  enumerable: false,
161
174
  configurable: false,
162
- value: () => authoringPhantom({ spelled: spellTokenRead({
163
- path,
164
- reader
165
- }) })
175
+ value: Object.freeze({
176
+ name: () => refKey(value.__ref),
177
+ var: phantom("cssVariable.var")
178
+ })
166
179
  });
167
180
  return value;
168
181
  }
@@ -2,27 +2,61 @@ import { Ref } from "../../framework/utils/refs.js";
2
2
  import { z } from "zod";
3
3
 
4
4
  //#region src/entities/system/calc.d.ts
5
- /** One side of the arithmetic: a number, a CSS length such as `0.25rem`, or a ref to a token. */
6
- type CalcOperand = number | string | Ref;
5
+ /**
6
+ * One term of the arithmetic: a number, a CSS length such as `0.25rem`, a ref to a token, or more
7
+ * arithmetic — `add(Token.ref('gap'), multiply(Token.ref('spacing'), 2))`.
8
+ */
9
+ type CalcOperand = number | string | Ref | Calc;
7
10
  interface Multiply {
8
11
  readonly multiply: readonly [CalcOperand, CalcOperand];
9
12
  }
10
13
  interface Divide {
11
14
  readonly divide: readonly [CalcOperand, CalcOperand];
12
15
  }
16
+ /** The terms summed, in order: `calc(a + b + c)`. */
17
+ interface Add {
18
+ readonly add: readonly CalcOperand[];
19
+ }
20
+ /** The first term less each of the rest, in order: `calc(a - b - c)`. */
21
+ interface Subtract {
22
+ readonly subtract: readonly CalcOperand[];
23
+ }
24
+ /** Any arithmetic on values. */
25
+ type Calc = Multiply | Divide | Add | Subtract;
13
26
  /** Whether a value is a `multiply(…)`. */
14
27
  declare function isMultiply(value: unknown): value is Multiply;
15
28
  /** Whether a value is a `divide(…)`. */
16
29
  declare function isDivide(value: unknown): value is Divide;
30
+ /** Whether a value is an `add(…)`. */
31
+ declare function isAdd(value: unknown): value is Add;
32
+ /** Whether a value is a `subtract(…)`. */
33
+ declare function isSubtract(value: unknown): value is Subtract;
34
+ /** Whether a value is arithmetic of any kind. */
35
+ declare function isCalc(value: unknown): value is Calc;
36
+ /** The CSS operator an arithmetic value applies between its terms. */
37
+ declare function calcOperator(value: Calc): '*' | '/' | '+' | '-';
38
+ /** An arithmetic value's terms, in order. */
39
+ declare function calcOperands(value: Calc): readonly CalcOperand[];
17
40
  /** `multiply(Token.ref('spacing'), 4)` — the first operand times the second. */
18
41
  declare function multiply(left: CalcOperand, right: CalcOperand): Multiply;
19
42
  /** `divide(1.25, 0.875)` — the first operand over the second. */
20
43
  declare function divide(left: CalcOperand, right: CalcOperand): Divide;
44
+ /** `add(Token.ref('gap'), Token.ref('spacing'))` — every term summed, two or more. */
45
+ declare function add(first: CalcOperand, second: CalcOperand, ...rest: CalcOperand[]): Add;
46
+ /** `subtract(Token.ref('track'), Token.ref('handle'))` — the first term less each of the rest. */
47
+ declare function subtract(first: CalcOperand, second: CalcOperand, ...rest: CalcOperand[]): Subtract;
21
48
  /**
22
49
  * What the arithmetic comes to, given each operand's literal — `1rem` for `0.25rem` times 4,
23
- * `1.4286` for `1.25` over `0.875` — or `undefined` when the operands are not plain quantities or
24
- * their units don't combine. `literal` resolves an operand (a ref to the token's value).
50
+ * `1.4286` for `1.25` over `0.875`, `1.5rem` for `1rem` plus `0.5rem` — or `undefined` when the
51
+ * operands are not plain quantities or their units don't combine. `literal` resolves an operand (a
52
+ * ref to the token's value); a term that is arithmetic itself is worked out first, with the same
53
+ * `literal`.
54
+ *
55
+ * A sum or a difference takes terms of one unit, since that is the only case that comes to one
56
+ * quantity here: `1rem + 4px` is real CSS the browser works out, and has no literal of its own.
57
+ * A plain number and a length don't add, in CSS or here: `calc(0 + 4px)` is invalid, so a zero
58
+ * term needs its unit (`'0px'`).
25
59
  */
26
- declare function evaluateCalc(value: Multiply | Divide, literal: (part: CalcOperand) => string | number | undefined): string | number | undefined;
60
+ declare function evaluateCalc(value: Calc, literal: (part: Exclude<CalcOperand, Calc>) => string | number | undefined): string | number | undefined;
27
61
  //#endregion
28
- export { CalcOperand, Divide, Multiply, divide, evaluateCalc, isDivide, isMultiply, multiply };
62
+ export { Add, Calc, CalcOperand, Divide, Multiply, Subtract, add, calcOperands, calcOperator, divide, evaluateCalc, isAdd, isCalc, isDivide, isMultiply, isSubtract, multiply, subtract };
@@ -3,28 +3,65 @@ import { z } from "zod";
3
3
  //#region src/entities/system/calc.ts
4
4
  /**
5
5
  * A value that is arithmetic on others — `multiply(Token.ref('spacing'), 4)` is
6
- * `calc(var(--spacing) * 4)`, and `divide(1.25, 0.875)` is `calc(1.25 / 0.875)`.
6
+ * `calc(var(--spacing) * 4)`, `divide(1.25, 0.875)` is `calc(1.25 / 0.875)`, and
7
+ * `add(Token.ref('gap'), Token.ref('spacing'))` is `calc(var(--gap) + var(--spacing))`.
7
8
  *
8
9
  * Held as its operands rather than as the `calc()` string so a ref in it stays a ref: a rename
9
10
  * follows into it, the dependency graph sees it, and a reader can work out what it comes to
10
11
  * (`spacing` at `0.25rem` times 4 is `1rem`) where a string would only be text.
11
12
  */
12
- const operand = z.union([
13
+ const operand = z.lazy(() => z.union([
13
14
  z.number(),
14
15
  z.string().min(1),
15
- refSchema
16
- ]);
16
+ refSchema,
17
+ multiplyValue,
18
+ divideValue,
19
+ addValue,
20
+ subtractValue
21
+ ]));
17
22
  const operands = z.tuple([operand, operand]);
23
+ /** A sum or a difference takes two terms or more, so `add(a, b, c)` needs no nesting. */
24
+ const terms = z.array(operand).min(2);
18
25
  const multiplyValue = z.strictObject({ multiply: operands });
19
26
  const divideValue = z.strictObject({ divide: operands });
27
+ const addValue = z.strictObject({ add: terms });
28
+ const subtractValue = z.strictObject({ subtract: terms });
20
29
  const isPair = (value) => Array.isArray(value) && value.length === 2;
30
+ const isTerms = (value) => Array.isArray(value) && value.length >= 2;
31
+ const isObject = (value) => typeof value === "object" && value !== null;
21
32
  /** Whether a value is a `multiply(…)`. */
22
33
  function isMultiply(value) {
23
- return typeof value === "object" && value !== null && isPair(value.multiply);
34
+ return isObject(value) && isPair(value.multiply);
24
35
  }
25
36
  /** Whether a value is a `divide(…)`. */
26
37
  function isDivide(value) {
27
- return typeof value === "object" && value !== null && isPair(value.divide);
38
+ return isObject(value) && isPair(value.divide);
39
+ }
40
+ /** Whether a value is an `add(…)`. */
41
+ function isAdd(value) {
42
+ return isObject(value) && isTerms(value.add);
43
+ }
44
+ /** Whether a value is a `subtract(…)`. */
45
+ function isSubtract(value) {
46
+ return isObject(value) && isTerms(value.subtract);
47
+ }
48
+ /** Whether a value is arithmetic of any kind. */
49
+ function isCalc(value) {
50
+ return isMultiply(value) || isDivide(value) || isAdd(value) || isSubtract(value);
51
+ }
52
+ /** The CSS operator an arithmetic value applies between its terms. */
53
+ function calcOperator(value) {
54
+ if (isMultiply(value)) return "*";
55
+ if (isDivide(value)) return "/";
56
+ if (isAdd(value)) return "+";
57
+ return "-";
58
+ }
59
+ /** An arithmetic value's terms, in order. */
60
+ function calcOperands(value) {
61
+ if (isMultiply(value)) return value.multiply;
62
+ if (isDivide(value)) return value.divide;
63
+ if (isAdd(value)) return value.add;
64
+ return value.subtract;
28
65
  }
29
66
  /** `multiply(Token.ref('spacing'), 4)` — the first operand times the second. */
30
67
  function multiply(left, right) {
@@ -34,15 +71,33 @@ function multiply(left, right) {
34
71
  function divide(left, right) {
35
72
  return { divide: [left, right] };
36
73
  }
37
- /** A `multiply(…)` as CSS — `calc(var(--spacing) * 4)` — with `render` resolving each operand. */
38
- function renderMultiply(value, render) {
39
- const [left, right] = value.multiply;
40
- return `calc(${render(left)} * ${render(right)})`;
74
+ /** `add(Token.ref('gap'), Token.ref('spacing'))` — every term summed, two or more. */
75
+ function add(first, second, ...rest) {
76
+ return { add: [
77
+ first,
78
+ second,
79
+ ...rest
80
+ ] };
81
+ }
82
+ /** `subtract(Token.ref('track'), Token.ref('handle'))` — the first term less each of the rest. */
83
+ function subtract(first, second, ...rest) {
84
+ return { subtract: [
85
+ first,
86
+ second,
87
+ ...rest
88
+ ] };
41
89
  }
42
- /** A `divide(…)` as CSS — `calc(1.25 / 0.875)`. */
43
- function renderDivide(value, render) {
44
- const [left, right] = value.divide;
45
- return `calc(${render(left)} / ${render(right)})`;
90
+ /**
91
+ * Arithmetic as CSS — `calc(var(--spacing) * 4)`, `calc(var(--gap) + var(--spacing))` — with
92
+ * `render` resolving each term. A term that is arithmetic itself is written as its parenthesised
93
+ * expression rather than a second `calc()`, which says the same and reads as the formula it is:
94
+ * `calc(var(--gap) + (var(--spacing) * 2))`. `+` and `-` are spaced, as CSS requires.
95
+ */
96
+ function renderCalc(value, render) {
97
+ return `calc(${calcOperands(value).map((part) => {
98
+ const css = render(part);
99
+ return isCalc(part) && css.startsWith("calc(") ? css.slice(4) : css;
100
+ }).join(` ${calcOperator(value)} `)})`;
46
101
  }
47
102
  function multiplyAuthoring(value) {
48
103
  return {
@@ -56,6 +111,18 @@ function divideAuthoring(value) {
56
111
  args: [...value.divide]
57
112
  };
58
113
  }
114
+ function addAuthoring(value) {
115
+ return {
116
+ helper: "add",
117
+ args: [...value.add]
118
+ };
119
+ }
120
+ function subtractAuthoring(value) {
121
+ return {
122
+ helper: "subtract",
123
+ args: [...value.subtract]
124
+ };
125
+ }
59
126
  const isDigit = (char) => char !== void 0 && char >= "0" && char <= "9";
60
127
  /**
61
128
  * The plain number a string starts with — an optional minus, digits, and an optional fraction
@@ -92,30 +159,54 @@ function quantityOf(value) {
92
159
  }
93
160
  /**
94
161
  * What the arithmetic comes to, given each operand's literal — `1rem` for `0.25rem` times 4,
95
- * `1.4286` for `1.25` over `0.875` — or `undefined` when the operands are not plain quantities or
96
- * their units don't combine. `literal` resolves an operand (a ref to the token's value).
162
+ * `1.4286` for `1.25` over `0.875`, `1.5rem` for `1rem` plus `0.5rem` — or `undefined` when the
163
+ * operands are not plain quantities or their units don't combine. `literal` resolves an operand (a
164
+ * ref to the token's value); a term that is arithmetic itself is worked out first, with the same
165
+ * `literal`.
166
+ *
167
+ * A sum or a difference takes terms of one unit, since that is the only case that comes to one
168
+ * quantity here: `1rem + 4px` is real CSS the browser works out, and has no literal of its own.
169
+ * A plain number and a length don't add, in CSS or here: `calc(0 + 4px)` is invalid, so a zero
170
+ * term needs its unit (`'0px'`).
97
171
  */
98
172
  function evaluateCalc(value, literal) {
99
- const [left, right] = isMultiply(value) ? value.multiply : value.divide;
100
- const a = quantityOf(literal(left));
101
- const b = quantityOf(literal(right));
102
- if (!a || !b) return void 0;
103
- let result;
104
- if (isMultiply(value)) {
105
- if (a.unit && b.unit) return void 0;
106
- result = {
107
- amount: a.amount * b.amount,
108
- unit: a.unit || b.unit
109
- };
110
- } else {
173
+ const quantities = [];
174
+ for (const part of calcOperands(value)) {
175
+ const quantity = quantityOf(isCalc(part) ? evaluateCalc(part, literal) : literal(part));
176
+ if (!quantity) return void 0;
177
+ quantities.push(quantity);
178
+ }
179
+ const result = combine(value, quantities);
180
+ if (!result) return void 0;
181
+ const amount = Math.round(result.amount * 1e4) / 1e4;
182
+ return result.unit ? `${amount}${result.unit}` : amount;
183
+ }
184
+ /** The terms' quantities combined by the value's operator, or `undefined` when their units don't. */
185
+ function combine(value, terms) {
186
+ if (isMultiply(value) || isDivide(value)) {
187
+ const [a, b] = terms;
188
+ if (isMultiply(value)) {
189
+ if (a.unit && b.unit) return void 0;
190
+ return {
191
+ amount: a.amount * b.amount,
192
+ unit: a.unit || b.unit
193
+ };
194
+ }
111
195
  if (b.amount === 0 || b.unit && b.unit !== a.unit) return void 0;
112
- result = {
196
+ return {
113
197
  amount: a.amount / b.amount,
114
198
  unit: b.unit ? "" : a.unit
115
199
  };
116
200
  }
117
- const amount = Math.round(result.amount * 1e4) / 1e4;
118
- return result.unit ? `${amount}${result.unit}` : amount;
201
+ const units = new Set(terms.map((term) => term.unit));
202
+ if (units.size > 1) return void 0;
203
+ const unit = [...units][0] ?? "";
204
+ const sign = isAdd(value) ? 1 : -1;
205
+ const [first, ...rest] = terms;
206
+ return {
207
+ amount: rest.reduce((sum, term) => sum + sign * term.amount, first.amount),
208
+ unit
209
+ };
119
210
  }
120
211
  //#endregion
121
- export { divide, divideAuthoring, divideValue, evaluateCalc, isDivide, isMultiply, leadingNumber, multiply, multiplyAuthoring, multiplyValue, renderDivide, renderMultiply };
212
+ export { add, addAuthoring, addValue, calcOperands, calcOperator, divide, divideAuthoring, divideValue, evaluateCalc, isAdd, isCalc, isDivide, isMultiply, isSubtract, leadingNumber, multiply, multiplyAuthoring, multiplyValue, renderCalc, subtract, subtractAuthoring, subtractValue };
@@ -1,12 +1,12 @@
1
1
  import { ValueType } from "../../css/value-type.js";
2
- import { Divide, Multiply } from "./calc.js";
2
+ import { Add, Divide, Multiply, Subtract } from "./calc.js";
3
3
  import { DerivedColor, Gradient, OklchColor } from "./color.js";
4
4
  import { Shadow } from "./shadow.js";
5
5
  import { Transition } from "./transition.js";
6
6
  import { z } from "zod";
7
7
 
8
8
  //#region src/entities/system/composed-values.d.ts
9
- type ComposedValue = Multiply | Divide | OklchColor | DerivedColor | Gradient | Transition | Shadow;
9
+ type ComposedValue = Multiply | Divide | Add | Subtract | OklchColor | DerivedColor | Gradient | Transition | Shadow;
10
10
  /** How the write engine spells a stored value: the helper's name and its positional arguments. */
11
11
  interface ComposedValueAuthoring {
12
12
  readonly helper: string;
@@ -1,11 +1,12 @@
1
- import { divideAuthoring, divideValue, evaluateCalc, isDivide, isMultiply, multiplyAuthoring, multiplyValue, renderDivide, renderMultiply } from "./calc.js";
1
+ import { addAuthoring, addValue, calcOperands, divideAuthoring, divideValue, evaluateCalc, isAdd, isCalc, isDivide, isMultiply, isSubtract, multiplyAuthoring, multiplyValue, renderCalc, subtractAuthoring, subtractValue } from "./calc.js";
2
2
  import { derivedColor, derivedColorAuthoring, gradient, gradientAuthoring, isDerivedColor, isGradient, isOklchColor, oklchAuthoring, oklchColor, renderDerivedColor, renderGradient, renderOklch } from "./color.js";
3
3
  import { isShadow, renderShadow, shadowAuthoring, shadowValue } from "./shadow.js";
4
4
  import { isTransition, renderTransition, transitionAuthoring, transitionValue } from "./transition.js";
5
5
  //#region src/entities/system/composed-values.ts
6
- /** Arithmetic as a formula: `spacing × 4`, `1.25 ÷ 0.875`. */
7
- function describeCalc(operator, [left, right], describe) {
8
- return `${describe(left)} ${operator} ${describe(right)}`;
6
+ /** Arithmetic as a formula: `spacing × 4`, `1.25 ÷ 0.875`, `gap + (spacing × 2)`. A term that is
7
+ * arithmetic itself is parenthesised, so the formula reads the way it computes. */
8
+ function describeCalc(operator, value, describe) {
9
+ return calcOperands(value).map((part) => isCalc(part) ? `(${describe(part)})` : describe(part)).join(` ${operator} `);
9
10
  }
10
11
  /** A derived colour as its op: `darken(brand, 10%)`, `mix(brand, white, 30%)`. */
11
12
  function describeDerivedColor(value, describe) {
@@ -23,19 +24,35 @@ const composedValueForms = [
23
24
  form({
24
25
  schema: multiplyValue,
25
26
  is: isMultiply,
26
- render: renderMultiply,
27
- describe: (value, describe) => describeCalc("×", value.multiply, describe),
27
+ render: renderCalc,
28
+ describe: (value, describe) => describeCalc("×", value, describe),
28
29
  evaluate: evaluateCalc,
29
30
  authoring: multiplyAuthoring
30
31
  }),
31
32
  form({
32
33
  schema: divideValue,
33
34
  is: isDivide,
34
- render: renderDivide,
35
- describe: (value, describe) => describeCalc("÷", value.divide, describe),
35
+ render: renderCalc,
36
+ describe: (value, describe) => describeCalc("÷", value, describe),
36
37
  evaluate: evaluateCalc,
37
38
  authoring: divideAuthoring
38
39
  }),
40
+ form({
41
+ schema: addValue,
42
+ is: isAdd,
43
+ render: renderCalc,
44
+ describe: (value, describe) => describeCalc("+", value, describe),
45
+ evaluate: evaluateCalc,
46
+ authoring: addAuthoring
47
+ }),
48
+ form({
49
+ schema: subtractValue,
50
+ is: isSubtract,
51
+ render: renderCalc,
52
+ describe: (value, describe) => describeCalc("−", value, describe),
53
+ evaluate: evaluateCalc,
54
+ authoring: subtractAuthoring
55
+ }),
39
56
  form({
40
57
  schema: oklchColor,
41
58
  is: isOklchColor,
@@ -1,5 +1,5 @@
1
1
  import { createSliceMemo } from "../../framework/memo.js";
2
- import { glyphComponentOf, glyphLibrariesOf, iconToolFor, iconToolsOf, resolveComponentProps } from "../../framework/projections.js";
2
+ import { glyphComponentOf, glyphLibrariesOf, iconToolFor, iconToolsOf, isCustomPropertyKey, resolveComponentProps } from "../../framework/projections.js";
3
3
  import { isValueSlot } from "../../framework/prop-surface.js";
4
4
  import { Component } from "./Component.js";
5
5
  import { parseGlyphType } from "../../spec/glyph-jsx.js";
@@ -290,7 +290,7 @@ function isDeclaredElementProp(config, elementType, propName) {
290
290
  const component = Component.get(config, elementType);
291
291
  if (component) {
292
292
  if (resolveComponentProps(config, component.path)[propName] !== void 0) return true;
293
- if (propName.startsWith("--")) return false;
293
+ if (isCustomPropertyKey(propName)) return false;
294
294
  return config.resolve("style-property", propName) === void 0;
295
295
  }
296
296
  const parsed = parseGlyphType(elementType);
@@ -1,5 +1,5 @@
1
1
  import { hasRefMarker } from "../../framework/utils/refs.js";
2
- import { isDivide, isMultiply } from "./calc.js";
2
+ import { isCalc } from "./calc.js";
3
3
  import { isOklchColor } from "./color.js";
4
4
  import { literalValueOf } from "../../framework/projections.js";
5
5
  import "../../configs/platform.js";
@@ -88,7 +88,7 @@ function prepareLinkedTokenBody({ path, body, source, config, destinationPlatfor
88
88
  */
89
89
  function nativeLiteral({ config, value, remBase }) {
90
90
  const marked = typeof value === "string" && hasRefMarker(value);
91
- const calc = isMultiply(value) || isDivide(value);
91
+ const calc = isCalc(value);
92
92
  if (!marked && !calc && !isOklchColor(value)) return value;
93
93
  const literal = literalValueOf(config, value);
94
94
  if (literal === void 0) return value;
@@ -4,7 +4,7 @@ import { defineSubEntity } from "../../framework/defineSubEntity.js";
4
4
  import { isCssProperty, kebabCase } from "../../css/values.js";
5
5
  import { valueSchemaOf } from "../../framework/value-domain.js";
6
6
  import { glyphLeafOf, glyphPackageRef } from "./glyph-identity.js";
7
- import { glyphPropsOfBody, refListOf, stylePropTokenPath } from "../../framework/projections.js";
7
+ import { glyphPropsOfBody, isCustomPropertyKey, refListOf, stylePropTokenPath } from "../../framework/projections.js";
8
8
  import { z } from "zod";
9
9
  //#region src/entities/system/style-bag.ts
10
10
  /** The explicit type annotation breaks the `z.lazy` self-reference cycle and keeps the
@@ -211,6 +211,7 @@ function stylePropertyBehind(config, cssProperty) {
211
211
  */
212
212
  function bagKeyIssue(config, key, declared, { cssBehind = true } = {}) {
213
213
  if (key.startsWith("data-") || key.startsWith("aria-")) return void 0;
214
+ if (isCustomPropertyKey(key)) return void 0;
214
215
  if (key.includes("-")) {
215
216
  const camel = camelCase(key);
216
217
  if (config.resolve("style-property", camel)) return `\`${key}\` is not a style property; a layer key is the style-prop name, \`${camel}\`.`;
@@ -100,13 +100,22 @@ declare function tokenExists({
100
100
  * where the token resolves to one value, which is what makes `px('brand')` on a colour an error.
101
101
  */
102
102
  interface RegisteredTokenValues {}
103
+ /** A group's readers of one token's custom property, by alias. */
104
+ interface TokenGroupVariableReader<All extends string = string> {
105
+ /** The custom property's name, `--uds-spacing-2_5`, for code that sets or reads the variable
106
+ * itself (`style.setProperty`, a third-party prop that takes a name). */
107
+ name(alias: All): string;
108
+ /** `var(--uds-spacing-2_5)`, the reference the stylesheet's own rules use, so it follows modes. */
109
+ var(alias: All): string;
110
+ }
103
111
  /**
104
- * One token group's readers. `var` is available for every token; the others only for the tokens the
105
- * build resolved to one value, so a mode-dependent token is a type error everywhere but `var`.
112
+ * One token group's readers. `cssVariable` is available for every token that declares one; the others
113
+ * only for the tokens the build resolved to one value, so a mode-dependent token is a type error
114
+ * everywhere but `cssVariable`.
106
115
  */
107
116
  interface TokenGroupReader<All extends string = string, Static extends string = string, Px extends string = string, Ms extends string = string> {
108
- /** `var(--uds-spacing-2_5)` — the reference the stylesheet's own rules use, so it follows modes. */
109
- var(alias: All): string;
117
+ /** The token's custom property: its `name` and the `var()` that reads it. */
118
+ readonly cssVariable: TokenGroupVariableReader<All>;
110
119
  /** The resolved CSS value, `'0.625rem'`. */
111
120
  value(alias: Static): string;
112
121
  /** The value as CSS pixels: `rem` against the page's root font size (`settings.css.rem`), `px`
@@ -118,15 +127,28 @@ interface TokenGroupReader<All extends string = string, Static extends string =
118
127
  /**
119
128
  * The readers a token ref offers, by what the build resolved that path to. A path the augmentation
120
129
  * knows narrows to the readers its value supports, so `.px()` on a colour is a compile error; a path it
121
- * does not know — before a build, or a template a prop fills in — keeps all four.
130
+ * does not know — before a build, or a template a prop fills in — keeps them all.
122
131
  */
123
132
  type TokenReaders<P extends string> = P extends keyof RegisteredTokenValues ? ReadersOf<RegisteredTokenValues[P]> : ReadersOf<{
124
133
  css: string;
125
134
  px: number;
126
135
  ms: number;
127
136
  }>;
137
+ /** A token ref's readers of the custom property it declares. */
138
+ interface TokenVariableReaders {
139
+ /**
140
+ * The variable's name. In a config it is the declaration key that sets the variable,
141
+ * `{ [Token.ref('button/scale-hover').cssVariable.name()]: Token.ref('scaleEffect/up') }`, in a
142
+ * style rule's layer bag, a global style or a style property's `properties` and `declarations`, so
143
+ * the variable is prefixed, renamed with the token and named through a link. In a render the build
144
+ * writes the name itself, `'--uds-button-scale-hover'`.
145
+ */
146
+ name(): string;
147
+ /** `var(--uds-spacing-2_5)`, the reference the stylesheet's own rules use, so it follows modes. */
148
+ var(): string;
149
+ }
128
150
  type ReadersOf<V> = {
129
- /** `var(--uds-spacing-2_5)` — the reference the stylesheet's own rules use, so it follows modes. */var(): string;
151
+ /** The token's custom property: its `name` and the `var()` that reads it. */readonly cssVariable: TokenVariableReaders;
130
152
  } & (V extends {
131
153
  css: string;
132
154
  } ? {
@@ -140,8 +162,9 @@ type ReadersOf<V> = {
140
162
  } ? {
141
163
  ms(): number;
142
164
  } : unknown);
143
- /** The reader names, which is what the build recognizes on a `Token.ref(…)`. */
144
- declare const TOKEN_READERS: readonly ["var", "value", "px", "ms"];
165
+ /** The reader names, which is what the build recognizes on a `Token.ref(…)`. A dotted name is read
166
+ * through its parts, `Token.ref(path).cssVariable.name()`. */
167
+ declare const TOKEN_READERS: readonly ["value", "px", "ms", "cssVariable.name", "cssVariable.var"];
145
168
  type TokenReader = (typeof TOKEN_READERS)[number];
146
169
  declare function isTokenReader(name: string): name is TokenReader;
147
170
  /** A read as an author spells it: `Token.ref("spacing/2.5").px()`. */
@@ -153,4 +176,4 @@ declare function spellTokenRead({
153
176
  reader: TokenReader;
154
177
  }): string;
155
178
  //#endregion
156
- export { RegisteredTokenValues, TOKEN_READERS, TokenGroupReader, TokenReader, TokenReaders, TokenValueEntry, TokenValueRow, TokenValueState, isTokenReader, parseTokenValue, rootFontSize, spellTokenRead, tokenExists, tokenValueRow, tokenValueRows };
179
+ export { RegisteredTokenValues, TOKEN_READERS, TokenGroupReader, TokenGroupVariableReader, TokenReader, TokenReaders, TokenValueEntry, TokenValueRow, TokenValueState, TokenVariableReaders, isTokenReader, parseTokenValue, rootFontSize, spellTokenRead, tokenExists, tokenValueRow, tokenValueRows };
@@ -144,12 +144,14 @@ function modeDecides(config, path, seen = /* @__PURE__ */ new Set()) {
144
144
  return source ? modeDecides(source, pathOf(target), seen) : false;
145
145
  });
146
146
  }
147
- /** The reader names, which is what the build recognizes on a `Token.ref(…)`. */
147
+ /** The reader names, which is what the build recognizes on a `Token.ref(…)`. A dotted name is read
148
+ * through its parts, `Token.ref(path).cssVariable.name()`. */
148
149
  const TOKEN_READERS = [
149
- "var",
150
150
  "value",
151
151
  "px",
152
- "ms"
152
+ "ms",
153
+ "cssVariable.name",
154
+ "cssVariable.var"
153
155
  ];
154
156
  function isTokenReader(name) {
155
157
  return TOKEN_READERS.includes(name);
@@ -72,7 +72,7 @@ interface RecordedSource {
72
72
  * The dense-counter `2` a short-lived scheme stamped before the chain existed reads as the mint
73
73
  * that replaced it (see `detectedWireVersion`).
74
74
  */
75
- declare const SERIALIZED_CONFIG_VERSION = 20261005212405;
75
+ declare const SERIALIZED_CONFIG_VERSION = 20261005220312;
76
76
  interface SerializedConfig {
77
77
  /** See {@link SERIALIZED_CONFIG_VERSION}. Always written; a stored artifact may lack it. */
78
78
  readonly version: number;