@yahoo/uds-create-config 4.57.2 → 5.0.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.
Files changed (38) hide show
  1. package/dist/configs/react-native-system.d.ts +3 -3
  2. package/dist/configs/system.d.ts +2 -2
  3. package/dist/configs/system.js +4 -0
  4. package/dist/css/value-type.js +2 -2
  5. package/dist/entities/native/NativeStyleProperty.d.ts +4 -4
  6. package/dist/entities/system/GlobalStyle.d.ts +2 -0
  7. package/dist/entities/system/GlobalStyle.js +10 -1
  8. package/dist/entities/system/Modifier.d.ts +1 -1
  9. package/dist/entities/system/Token.js +21 -8
  10. package/dist/entities/system/calc.d.ts +40 -6
  11. package/dist/entities/system/calc.js +123 -32
  12. package/dist/entities/system/composed-values.d.ts +2 -2
  13. package/dist/entities/system/composed-values.js +25 -8
  14. package/dist/entities/system/icon-library.js +2 -2
  15. package/dist/entities/system/linked-token-values.js +2 -2
  16. package/dist/entities/system/modifier-axis.d.ts +1 -1
  17. package/dist/entities/system/style-bag.js +2 -1
  18. package/dist/entities/system/token-values.d.ts +32 -9
  19. package/dist/entities/system/token-values.js +5 -3
  20. package/dist/framework/Config.d.ts +1 -1
  21. package/dist/framework/Config.js +1 -1
  22. package/dist/framework/class-names.js +2 -2
  23. package/dist/framework/projections.d.ts +19 -4
  24. package/dist/framework/projections.js +33 -9
  25. package/dist/framework/schema-version.d.ts +1 -1
  26. package/dist/framework/schema-version.js +5 -5
  27. package/dist/framework/source-integrity.d.ts +1 -1
  28. package/dist/framework/utils/refs.d.ts +1 -2
  29. package/dist/framework/utils/refs.js +0 -5
  30. package/dist/index.d.ts +7 -7
  31. package/dist/index.js +3 -3
  32. package/dist/migrations/2.0.0/v1-artifact.d.ts +2 -2
  33. package/dist/migrations/20261005212405_global_style_on.d.ts +11 -0
  34. package/dist/migrations/20261005212405_global_style_on.js +10 -0
  35. package/dist/migrations/20261005220312_add_and_subtract.d.ts +13 -0
  36. package/dist/migrations/20261005220312_add_and_subtract.js +10 -0
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +2 -2
@@ -7,6 +7,7 @@ import { EntityClass } from "../framework/defineEntity.js";
7
7
  import { ConfigClass } from "../framework/defineConfig.js";
8
8
  import { Named } from "../framework/named.js";
9
9
  import { PackageClass } from "../entities/system/Package.js";
10
+ import { CompositeClass } from "../entities/system/Composite.js";
10
11
  import { OverrideCondition } from "../entities/system/Token.js";
11
12
  import { StylePropValue } from "../entities/system/StyleProperty.js";
12
13
  import { ComponentClass } from "../entities/system/Component.js";
@@ -14,7 +15,6 @@ import { LinkedSystemClass } from "../entities/system/LinkedSystem.js";
14
15
  import { IconClass } from "../entities/system/Icon.js";
15
16
  import { NativeModifierClass } from "../entities/native/NativeModifier.js";
16
17
  import { CanvasRoleClass } from "../entities/system/CanvasRole.js";
17
- import { CompositeClass } from "../entities/system/Composite.js";
18
18
  import { DeviceClass } from "../entities/system/Device.js";
19
19
  import { FileClass } from "../entities/system/File.js";
20
20
  import { FontClass } from "../entities/system/Font.js";
@@ -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";
@@ -1,20 +1,20 @@
1
1
  import { ConfigClass } from "../framework/defineConfig.js";
2
2
  import { Named } from "../framework/named.js";
3
3
  import { PackageClass } from "../entities/system/Package.js";
4
+ import { CompositeClass } from "../entities/system/Composite.js";
5
+ import { ModifierClass } from "../entities/system/Modifier.js";
4
6
  import { TokenClass } from "../entities/system/Token.js";
5
7
  import { StylePropertyClass } from "../entities/system/StyleProperty.js";
6
8
  import { ComponentClass } from "../entities/system/Component.js";
7
9
  import { LinkedSystemClass } from "../entities/system/LinkedSystem.js";
8
10
  import { IconClass } from "../entities/system/Icon.js";
9
11
  import { CanvasRoleClass } from "../entities/system/CanvasRole.js";
10
- import { CompositeClass } from "../entities/system/Composite.js";
11
12
  import { DeviceClass } from "../entities/system/Device.js";
12
13
  import { FileClass } from "../entities/system/File.js";
13
14
  import { FontClass } from "../entities/system/Font.js";
14
15
  import { GlobalStyleClass } from "../entities/system/GlobalStyle.js";
15
16
  import { GuidanceClass } from "../entities/system/Guidance.js";
16
17
  import { GuidanceStyleClass } from "../entities/system/GuidanceStyle.js";
17
- import { ModifierClass } from "../entities/system/Modifier.js";
18
18
  import { MotionClass } from "../entities/system/Motion.js";
19
19
  import { OperationClass } from "../entities/system/Operation.js";
20
20
  import { SettingsClass } from "../entities/system/Settings.js";
@@ -54,6 +54,8 @@ import { styleRulesAsListMigration } from "../migrations/20261001112308_style_ru
54
54
  import { motionExitAndElementDuplicateMigration } from "../migrations/20261001130958_motion_exit_and_element_duplicate.js";
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
+ import { globalStyleOnMigration } from "../migrations/20261005212405_global_style_on.js";
58
+ import { addAndSubtractMigration } from "../migrations/20261005220312_add_and_subtract.js";
57
59
  const System = defineConfig({
58
60
  name: WEB_CONFIG_NAME,
59
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.",
@@ -119,5 +121,7 @@ if (!registeredSchemaMigrations().has(20261001112308)) registerSchemaMigrations(
119
121
  if (!registeredSchemaMigrations().has(20261001130958)) registerSchemaMigrations(motionExitAndElementDuplicateMigration);
120
122
  if (!registeredSchemaMigrations().has(20261001131e3)) registerSchemaMigrations(layerStyleAndForwardOpsMigration);
121
123
  if (!registeredSchemaMigrations().has(20261001162046)) registerSchemaMigrations(oklchAndEmitFieldsMigration);
124
+ if (!registeredSchemaMigrations().has(20261005212405)) registerSchemaMigrations(globalStyleOnMigration);
125
+ if (!registeredSchemaMigrations().has(20261005220312)) registerSchemaMigrations(addAndSubtractMigration);
122
126
  //#endregion
123
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,6 +1,7 @@
1
1
  import { DerivedColor, OklchColor } from "./color.js";
2
2
  import { EntityClass } from "../../framework/defineEntity.js";
3
3
  import { Named } from "../../framework/named.js";
4
+ import { ModifierClass } from "./Modifier.js";
4
5
  import { z } from "zod";
5
6
 
6
7
  //#region src/entities/system/GlobalStyle.d.ts
@@ -17,6 +18,7 @@ declare const GlobalStyleDefinition: EntityClass<z.ZodObject<{
17
18
  selector: z.ZodString;
18
19
  declarations: z.ZodRecord<z.ZodString, z.ZodType<Declared, unknown, z.core.$ZodTypeInternals<Declared, unknown>>>;
19
20
  important: z.ZodOptional<z.ZodBoolean>;
21
+ on: z.ZodOptional<z.ZodUnion<readonly [ModifierClass, z.ZodArray<ModifierClass>]>>;
20
22
  }, z.core.$strict>, z.ZodObject<Record<never, never>, z.core.$strip>, "global-style", {}, Record<never, never>, false, Record<never, never>, Record<never, never>>;
21
23
  interface GlobalStyleClass extends Named<typeof GlobalStyleDefinition> {}
22
24
  declare const GlobalStyle: GlobalStyleClass;
@@ -1,6 +1,7 @@
1
1
  import { refSchema } from "../../framework/utils/refs.js";
2
2
  import { derivedColor, oklchColor } from "./color.js";
3
3
  import { defineEntity } from "../../framework/defineEntity.js";
4
+ import { overrideConditionSchema } from "./Token.js";
4
5
  import { z } from "zod";
5
6
  //#region src/entities/system/GlobalStyle.ts
6
7
  /**
@@ -41,7 +42,15 @@ const GlobalStyle = defineEntity({
41
42
  * layered and a third party's stylesheet (React Flow, CodeMirror's injected theme) is not, so a
42
43
  * rule meant to beat one has to say so; the flag lets it while its values stay refs.
43
44
  */
44
- important: z.boolean().optional().describe("Emit every declaration `!important`, to beat a third party's unlayered stylesheet.")
45
+ important: z.boolean().optional().describe("Emit every declaration `!important`, to beat a third party's unlayered stylesheet."),
46
+ /**
47
+ * The modifier the rule applies under, or several that all hold at once — the same shape as a
48
+ * token override's `on`. The rule is written through each modifier's own activation, as a
49
+ * component rule is: a state composes onto the selector, a selector mode stands before it as its
50
+ * ancestor (`:root` takes the mode's selector itself), and a media query wraps it. A ref, so
51
+ * renaming the modifier cascades into the rule instead of leaving a hand-copied `.dark` behind.
52
+ */
53
+ on: overrideConditionSchema.optional().describe("The modifier the rule applies under, or a list that all hold at once: `{ \"__ref\": \"modifier:colorMode/dark\" }`. A state composes onto the selector, a selector mode is its ancestor (`:root` takes the mode itself), a media query wraps the rule. Rules inside `@media` or `@supports` take it too; `@keyframes` and `@font-face` are written as they are.")
45
54
  })
46
55
  });
47
56
  /**
@@ -1,8 +1,8 @@
1
1
  import { EntityClass } from "../../framework/defineEntity.js";
2
2
  import { Named } from "../../framework/named.js";
3
- import { Config } from "../../framework/Config.js";
4
3
  import { CompositeClass } from "./Composite.js";
5
4
  import { modifierAxis, modifierCategory } from "./modifier-axis.js";
5
+ import { Config } from "../../framework/Config.js";
6
6
  import { z } from "zod";
7
7
 
8
8
  //#region src/entities/system/Modifier.d.ts
@@ -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,16 +156,29 @@ 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
  }
169
182
  const Token = Object.assign(TokenEntity, { ref: tokenRef });
170
183
  //#endregion
171
- export { Token, defineOverride, overrideCondition, overrideKey, overrideKeyModifiers, overrideModifiers };
184
+ export { Token, defineOverride, overrideCondition, overrideConditionSchema, overrideKey, overrideKeyModifiers, overrideModifiers };
@@ -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;
@@ -1,5 +1,5 @@
1
- import { Config } from "../../framework/Config.js";
2
1
  import { ModifierCategory, ModifierGroupMeta } from "./Modifier.js";
2
+ import { Config } from "../../framework/Config.js";
3
3
 
4
4
  //#region src/entities/system/modifier-axis.d.ts
5
5
  /**
@@ -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}\`.`;