@yahoo/uds-create-config 4.53.2 → 4.55.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 (64) hide show
  1. package/dist/capture/capture-to-spec.js +22 -6
  2. package/dist/capture/class-grammar.js +2 -3
  3. package/dist/configs/platform.d.ts +8 -1
  4. package/dist/configs/react-native-system.d.ts +21 -9
  5. package/dist/configs/react-native-system.js +1 -1
  6. package/dist/configs/system.js +3 -1
  7. package/dist/css/value-type.js +24 -0
  8. package/dist/entities/documentation/Documentation.d.ts +1 -1
  9. package/dist/entities/native/NativeStyleProperty.d.ts +14 -9
  10. package/dist/entities/native/NativeStyleProperty.js +23 -2
  11. package/dist/entities/native/NativeToken.d.ts +10 -3
  12. package/dist/entities/native/NativeToken.js +39 -4
  13. package/dist/entities/system/Component.js +2 -1
  14. package/dist/entities/system/GlobalStyle.d.ts +2 -2
  15. package/dist/entities/system/GlobalStyle.js +2 -1
  16. package/dist/entities/system/Modifier.d.ts +3 -1
  17. package/dist/entities/system/Modifier.js +3 -1
  18. package/dist/entities/system/Settings.d.ts +9 -1
  19. package/dist/entities/system/Settings.js +6 -2
  20. package/dist/entities/system/StyleProperty.d.ts +39 -3
  21. package/dist/entities/system/StyleProperty.js +141 -18
  22. package/dist/entities/system/Token.d.ts +14 -1
  23. package/dist/entities/system/Token.js +21 -3
  24. package/dist/entities/system/calc.d.ts +28 -0
  25. package/dist/entities/system/calc.js +121 -0
  26. package/dist/entities/system/color.d.ts +29 -4
  27. package/dist/entities/system/color.js +61 -8
  28. package/dist/entities/system/component-module.js +15 -4
  29. package/dist/entities/system/composed-values.d.ts +3 -2
  30. package/dist/entities/system/composed-values.js +20 -1
  31. package/dist/entities/system/defineComponent.d.ts +3 -3
  32. package/dist/entities/system/link-plan.js +10 -1
  33. package/dist/entities/system/linked-token-values.d.ts +34 -0
  34. package/dist/entities/system/linked-token-values.js +135 -0
  35. package/dist/entities/system/shadow.d.ts +9 -7
  36. package/dist/entities/system/shadow.js +8 -7
  37. package/dist/entities/system/style-bag.d.ts +1 -1
  38. package/dist/entities/system/token-index.js +45 -2
  39. package/dist/entities/system/token-values.d.ts +3 -2
  40. package/dist/entities/system/token-values.js +9 -9
  41. package/dist/framework/Config.d.ts +14 -18
  42. package/dist/framework/Config.js +105 -55
  43. package/dist/framework/class-names.d.ts +47 -7
  44. package/dist/framework/class-names.js +115 -14
  45. package/dist/framework/defineEntity.d.ts +3 -1
  46. package/dist/framework/defineEntity.js +3 -1
  47. package/dist/framework/inline-sources.d.ts +3 -3
  48. package/dist/framework/inline-sources.js +1 -1
  49. package/dist/framework/projections.d.ts +84 -3
  50. package/dist/framework/projections.js +315 -20
  51. package/dist/framework/ref-graph.js +1 -1
  52. package/dist/framework/registry.d.ts +7 -0
  53. package/dist/framework/registry.js +2 -1
  54. package/dist/framework/schema-version.d.ts +1 -1
  55. package/dist/framework/schema-version.js +5 -5
  56. package/dist/framework/utils/group.d.ts +2 -1
  57. package/dist/framework/utils/refs.d.ts +19 -2
  58. package/dist/framework/utils/refs.js +100 -12
  59. package/dist/index.d.ts +12 -10
  60. package/dist/index.js +10 -8
  61. package/dist/migrations/20261001162046_oklch_and_emit_fields.d.ts +25 -0
  62. package/dist/migrations/20261001162046_oklch_and_emit_fields.js +10 -0
  63. package/dist/tsconfig.tsbuildinfo +1 -1
  64. package/package.json +2 -2
@@ -16,9 +16,59 @@ import { z } from "zod";
16
16
  * That's also why this isn't a separate entity: a derived colour is a value, and making it its own
17
17
  * kind would split the token store and break every `token:` ref that points into it.
18
18
  */
19
+ const oklchColor = z.strictObject({ oklch: z.strictObject({
20
+ l: z.number().min(0).max(100),
21
+ c: z.number().min(0),
22
+ h: z.number().optional(),
23
+ alpha: z.number().min(0).max(1).optional()
24
+ }) });
25
+ /** Whether a value is an OKLCH colour. */
26
+ function isOklchColor(value) {
27
+ const channels = value?.oklch;
28
+ return typeof value === "object" && value !== null && typeof channels === "object" && channels !== null && typeof channels.l === "number" && typeof channels.c === "number";
29
+ }
30
+ /** `oklch(63.7, 0.237, 25.331)` — lightness as a percentage, chroma, and hue in degrees. A grey
31
+ * leaves the hue out. */
32
+ function oklch(l, c, h, alpha) {
33
+ return { oklch: {
34
+ l,
35
+ c,
36
+ ...h === void 0 ? {} : { h },
37
+ ...alpha === void 0 ? {} : { alpha }
38
+ } };
39
+ }
40
+ /** An OKLCH colour as CSS: `oklch(63.7% 0.237 25.331)`, `oklch(98.5% 0 none)`, `oklch(… / 0.5)`. */
41
+ function renderOklch(value) {
42
+ const { l, c, h, alpha: opacity } = value.oklch;
43
+ const channels = `${l}% ${c} ${h ?? "none"}`;
44
+ return opacity === void 0 ? `oklch(${channels})` : `oklch(${channels} / ${opacity})`;
45
+ }
46
+ /** The `oklch(…)` call that authors a stored OKLCH colour. One with an alpha and no hue prints as
47
+ * the object it is, since the helper takes the hue before the alpha. */
48
+ function oklchAuthoring(value) {
49
+ const { l, c, h, alpha: opacity } = value.oklch;
50
+ if (h === void 0) return opacity === void 0 ? {
51
+ helper: "oklch",
52
+ args: [l, c]
53
+ } : void 0;
54
+ return {
55
+ helper: "oklch",
56
+ args: opacity === void 0 ? [
57
+ l,
58
+ c,
59
+ h
60
+ ] : [
61
+ l,
62
+ c,
63
+ h,
64
+ opacity
65
+ ]
66
+ };
67
+ }
19
68
  const colorValue = z.lazy(() => z.union([
20
69
  z.string(),
21
70
  refSchema,
71
+ oklchColor,
22
72
  derivedColor,
23
73
  gradient
24
74
  ]));
@@ -71,6 +121,8 @@ const DERIVED_COLOR_SPACE = {
71
121
  alpha: "srgb",
72
122
  mix: "oklch"
73
123
  };
124
+ /** The space a derived colour with no `space` of its own mixes in, on every platform. */
125
+ const DERIVED_COLOR_DEFAULT_SPACE = "oklch";
74
126
  /** Blend toward black in light mode and white in dark — so a hover state darkens in both. */
75
127
  function darken(color, amount) {
76
128
  return {
@@ -89,12 +141,13 @@ function lighten(color, amount) {
89
141
  space: DERIVED_COLOR_SPACE.lighten
90
142
  };
91
143
  }
92
- /** `alpha(color, 0.2)` — the colour at 20% opacity. Takes a 0–1 opacity, like CSS. */
144
+ /** `alpha(color, 0.2)` — the colour at 20% opacity. Takes a 0–1 opacity, like CSS, kept to a tenth
145
+ * of a percent. */
93
146
  function alpha(color, opacity) {
94
147
  return {
95
148
  op: "alpha",
96
149
  color,
97
- amount: Math.round(Math.max(0, Math.min(1, opacity)) * 100),
150
+ amount: Math.round(Math.max(0, Math.min(1, opacity)) * 1e3) / 10,
98
151
  space: DERIVED_COLOR_SPACE.alpha
99
152
  };
100
153
  }
@@ -110,7 +163,7 @@ function mix(color, towards, amount, space = DERIVED_COLOR_SPACE.mix) {
110
163
  }
111
164
  /**
112
165
  * The helper call that authors a stored derived colour, or `undefined` for one no helper produces (a
113
- * `darken` in another space, an `alpha` at a fractional percent). `alpha` stores a 0–100 percent and
166
+ * `darken` in another space, an `alpha` finer than a tenth of a percent). `alpha` stores a 0–100 percent and
114
167
  * takes a 0–1 opacity, so the argument is the percent scaled back. A `darken`, `lighten` or `mix`
115
168
  * with no `space` renders in `oklch`, so it is the helper's own value; an `alpha` with none renders
116
169
  * in `oklch` too, which is not what `alpha()` writes, so it stays an object.
@@ -124,12 +177,12 @@ function derivedColorAuthoring(value) {
124
177
  };
125
178
  }
126
179
  if (value.op === "alpha") {
127
- const whole = Number.isInteger(value.amount);
180
+ const whole = Number.isInteger(value.amount * 10);
128
181
  const inRange = value.amount >= 0 && value.amount <= 100;
129
182
  if (value.space !== DERIVED_COLOR_SPACE.alpha || value.with !== void 0 || !whole || !inRange) return;
130
183
  return {
131
184
  helper: "alpha",
132
- args: [value.color, value.amount / 100]
185
+ args: [value.color, Math.round(value.amount * 10) / 1e3]
133
186
  };
134
187
  }
135
188
  if (value.with === void 0) return void 0;
@@ -169,9 +222,9 @@ function renderDerivedColor(value, render) {
169
222
  }
170
223
  /** A gradient as CSS — `linear-gradient(to right, transparent 0%, var(--uds-bg-primary) 50%)`. */
171
224
  function renderGradient(value, render) {
172
- const stops = value.stops.map((stop) => [render(stop.color), stop.at].filter(Boolean).join(" ")).join(", ");
173
- const head = [value.angle, value.space ? `in ${value.space}` : void 0].filter(Boolean);
225
+ const stops = value.stops.map((stop) => [render(stop.color), stop.at === void 0 ? void 0 : render(stop.at)].filter(Boolean).join(" ")).join(", ");
226
+ const head = [value.angle === void 0 ? void 0 : render(value.angle), value.space ? `in ${value.space}` : void 0].filter(Boolean);
174
227
  return `${value.repeating ? "repeating-" : ""}${value.gradient}-gradient(${[...head, stops].join(", ")})`;
175
228
  }
176
229
  //#endregion
177
- export { DERIVED_COLOR_SPACE, alpha, darken, derivedColor, derivedColorAuthoring, gradient, gradientAuthoring, isDerivedColor, isGradient, lighten, linearGradient, mix, renderDerivedColor, renderGradient };
230
+ export { DERIVED_COLOR_DEFAULT_SPACE, DERIVED_COLOR_SPACE, alpha, darken, derivedColor, derivedColorAuthoring, gradient, gradientAuthoring, isDerivedColor, isGradient, isOklchColor, lighten, linearGradient, mix, oklch, oklchAuthoring, oklchColor, renderDerivedColor, renderGradient, renderOklch };
@@ -1,10 +1,10 @@
1
1
  import { ref, splitRef } from "../../framework/utils/refs.js";
2
2
  import { createSliceMemo } from "../../framework/memo.js";
3
- import { Settings, classPrefixOf, varPrefixOf } from "./Settings.js";
3
+ import { Settings, classPrefixOf, prefixed, varPrefixOf } from "./Settings.js";
4
4
  import { File } from "./File.js";
5
+ import { isNativeConfig } from "../../configs/platform.js";
5
6
  import { Component } from "./Component.js";
6
7
  import { contentKey } from "../../framework/utils/content-key.js";
7
- import { isNativeConfig } from "../../configs/platform.js";
8
8
  //#region src/entities/system/component-module.ts
9
9
  /**
10
10
  * What names a compiled component module: a content key over the module's config-visible build
@@ -278,7 +278,10 @@ function componentModuleSource(config, componentPathOrRef) {
278
278
  * three name the same classes and hash the same config.
279
279
  */
280
280
  function linkedModuleView({ consumer, owner, slug, shadowed }) {
281
- const classNames = shadowed ? `${classPrefixOf(consumer)}-${slug}` : classPrefixOf(consumer);
281
+ const classNames = shadowed ? prefixed({
282
+ prefix: classPrefixOf(consumer),
283
+ name: slug
284
+ }) : classPrefixOf(consumer);
282
285
  return owner.apply({
283
286
  kind: "settings",
284
287
  operation: "update",
@@ -353,7 +356,7 @@ function linkedComponentModule(config, componentPathOrRef) {
353
356
  moduleKey,
354
357
  exportName: componentModuleExportName(authored?.path ?? component.path),
355
358
  linked: {
356
- slug,
359
+ slug: slug.split("~").at(-1) ?? slug,
357
360
  ...pin
358
361
  }
359
362
  },
@@ -365,6 +368,14 @@ function linkedComponentModule(config, componentPathOrRef) {
365
368
  * bridge this config links, not by this config.
366
369
  */
367
370
  function publishedPin(config, slug) {
371
+ if (slug.includes("~")) {
372
+ const segments = slug.split("~");
373
+ const { systemId, version } = (config.sourceFor(segments.slice(0, -1).join("~"))?.resolve("linked-system", segments.at(-1) ?? ""))?.toJSON() ?? {};
374
+ return systemId && version ? {
375
+ systemId,
376
+ version
377
+ } : void 0;
378
+ }
368
379
  const seen = /* @__PURE__ */ new Set();
369
380
  const queue = [config];
370
381
  for (let at = queue.shift(); at; at = queue.shift()) {
@@ -1,10 +1,11 @@
1
- import { DerivedColor, Gradient } from "./color.js";
1
+ import { Divide, Multiply } from "./calc.js";
2
+ import { DerivedColor, Gradient, OklchColor } from "./color.js";
2
3
  import { Shadow } from "./shadow.js";
3
4
  import { Transition } from "./transition.js";
4
5
  import { z } from "zod";
5
6
 
6
7
  //#region src/entities/system/composed-values.d.ts
7
- type ComposedValue = DerivedColor | Gradient | Transition | Shadow;
8
+ type ComposedValue = Multiply | Divide | OklchColor | DerivedColor | Gradient | Transition | Shadow;
8
9
  /** How the write engine spells a stored value: the helper's name and its positional arguments. */
9
10
  interface ComposedValueAuthoring {
10
11
  readonly helper: string;
@@ -1,4 +1,5 @@
1
- import { derivedColor, derivedColorAuthoring, gradient, gradientAuthoring, isDerivedColor, isGradient, renderDerivedColor, renderGradient } from "./color.js";
1
+ import { divideAuthoring, divideValue, isDivide, isMultiply, multiplyAuthoring, multiplyValue, renderDivide, renderMultiply } from "./calc.js";
2
+ import { derivedColor, derivedColorAuthoring, gradient, gradientAuthoring, isDerivedColor, isGradient, isOklchColor, oklchAuthoring, oklchColor, renderDerivedColor, renderGradient, renderOklch } from "./color.js";
2
3
  import { isShadow, renderShadow, shadowAuthoring, shadowValue } from "./shadow.js";
3
4
  import { isTransition, renderTransition, transitionAuthoring, transitionValue } from "./transition.js";
4
5
  //#region src/entities/system/composed-values.ts
@@ -8,6 +9,24 @@ function form(spec) {
8
9
  return spec;
9
10
  }
10
11
  const composedValueForms = [
12
+ form({
13
+ schema: multiplyValue,
14
+ is: isMultiply,
15
+ render: renderMultiply,
16
+ authoring: multiplyAuthoring
17
+ }),
18
+ form({
19
+ schema: divideValue,
20
+ is: isDivide,
21
+ render: renderDivide,
22
+ authoring: divideAuthoring
23
+ }),
24
+ form({
25
+ schema: oklchColor,
26
+ is: isOklchColor,
27
+ render: renderOklch,
28
+ authoring: oklchAuthoring
29
+ }),
11
30
  form({
12
31
  schema: derivedColor,
13
32
  is: isDerivedColor,
@@ -499,10 +499,10 @@ type Registered = [keyof RegisteredStyleProps] extends [never] ? false : true;
499
499
  /** Every style prop the system registers, typed by the values it registers. */
500
500
  type StyleProps = { [K in keyof RegisteredStyleProps]?: RegisteredStyleProps[K] };
501
501
  /**
502
- * A modifier's block: the same style props, applied under its condition. One level deep, because
503
- * the runtime reads one — a block inside a block is a key it hands to the element.
502
+ * A modifier's block: the same style props, applied under its condition. A block inside a block
503
+ * stacks onto it in the order written, so `_md={{ _hover: { … } }}` is `md:hover:`.
504
504
  */
505
- type ModifierProps = { [K in keyof RegisteredModifiers]?: StyleProps };
505
+ type ModifierProps = { [K in keyof RegisteredModifiers]?: StyleProps & ModifierProps };
506
506
  /** The per-layer override channel the emitted module reads, keyed by this component's layers. */
507
507
  type LayerProps<L extends LayerMap> = {
508
508
  layerProps?: Partial<Record<keyof L & string, Record<string, unknown>>>;
@@ -168,7 +168,16 @@ function planLink({ config, input }) {
168
168
  if (path === "*") continue;
169
169
  rejectData(`Cannot link "${path}" through "${slug}" — the source has no ${kind} there.`, ["data", accessor]);
170
170
  }
171
- const taken = entries.filter((entry) => shadow(kind, entry.from));
171
+ const taken = entries.filter((entry) => {
172
+ const fit = config.canBorrow({
173
+ kind,
174
+ path: entry.from,
175
+ slug
176
+ });
177
+ if (fit.ok) return true;
178
+ if (source.resolve(kind, path)) rejectData(fit.error, ["data", accessor]);
179
+ return false;
180
+ }).filter((entry) => shadow(kind, entry.from));
172
181
  if (taken.length === entries.length) {
173
182
  if (path !== "*") gained.push(path);
174
183
  continue;
@@ -0,0 +1,34 @@
1
+ import { Config } from "../../framework/Config.js";
2
+ import { NATIVE_CONFIG_NAME, WEB_CONFIG_NAME } from "../../configs/platform.js";
3
+
4
+ //#region src/entities/system/linked-token-values.d.ts
5
+ type Platform = typeof WEB_CONFIG_NAME | typeof NATIVE_CONFIG_NAME;
6
+ declare function convertLinkedTokenValue({
7
+ path,
8
+ value,
9
+ sourcePlatform,
10
+ destinationPlatform,
11
+ remBase
12
+ }: {
13
+ path: string;
14
+ value: string | number | boolean;
15
+ sourcePlatform: string;
16
+ destinationPlatform: Platform; /** The pinned web source's fixed root size, only needed for web rem → native points. */
17
+ remBase?: number;
18
+ }): string | number | boolean;
19
+ /** The one adapter for base and mode values before a linked body is parsed by its destination. */
20
+ declare function prepareLinkedTokenBody({
21
+ path,
22
+ body,
23
+ source,
24
+ config,
25
+ destinationPlatform
26
+ }: {
27
+ path: string;
28
+ body: Record<string, unknown>;
29
+ source: Config; /** The config whose refs the body spells; a source's own body spells them in the source. */
30
+ config: Config;
31
+ destinationPlatform: Platform;
32
+ }): Record<string, unknown>;
33
+ //#endregion
34
+ export { convertLinkedTokenValue, prepareLinkedTokenBody };
@@ -0,0 +1,135 @@
1
+ import { hasRefMarker } from "../../framework/utils/refs.js";
2
+ import { isDivide, isMultiply } from "./calc.js";
3
+ import { isOklchColor } from "./color.js";
4
+ import { literalValueOf } from "../../framework/projections.js";
5
+ import "../../configs/platform.js";
6
+ import { rootFontSize } from "./token-values.js";
7
+ //#region src/entities/system/linked-token-values.ts
8
+ /**
9
+ * Adapt a linked token for the system reading it. The source keeps its authored value; only the
10
+ * destination's resolved view changes. Length groups are explicit because a bare native number
11
+ * does not say whether CSS should spell it as px or as a unitless value.
12
+ */
13
+ const LENGTH_GROUPS = new Set([
14
+ "spacing",
15
+ "radius",
16
+ "borderRadius",
17
+ "iconSize",
18
+ "borderWidth"
19
+ ]);
20
+ const NUMBER_GROUPS = new Set(["opacity", "zIndex"]);
21
+ const LENGTH = /^(-?(?:\d+(?:\.\d+)?|\.\d+))(px|rem)$/;
22
+ const NUMBER = /^-?(?:\d+(?:\.\d+)?|\.\d+)$/;
23
+ /** `oklch(L C H / A)`, the colour notation a config writes and React Native's colour parser
24
+ * doesn't read. Lightness and alpha may be percentages, the hue `none`. */
25
+ const OKLCH = /^oklch\(\s*(-?[\d.]+)(%?)\s+(-?[\d.]+)(%?)\s+(-?[\d.]+|none)(?:deg)?\s*(?:\/\s*(-?[\d.]+)(%?)\s*)?\)$/i;
26
+ function convertLinkedTokenValue({ path, value, sourcePlatform, destinationPlatform, remBase }) {
27
+ if (sourcePlatform === destinationPlatform) return value;
28
+ const group = path.split("/")[0];
29
+ if (destinationPlatform === "react-native-system") {
30
+ if (LENGTH_GROUPS.has(group)) {
31
+ if (typeof value === "number") return value;
32
+ const match = typeof value === "string" ? LENGTH.exec(value) : null;
33
+ if (!match) throw unsupported("native", path, value, "a px or rem length");
34
+ if (match[2] === "rem" && remBase === void 0) throw new Error(`[uds] Linked native token "${path}" has value ${JSON.stringify(value)}, but its source has no fixed root font size for rem conversion.`);
35
+ return Number(match[1]) * (match[2] === "rem" ? remBase : 1);
36
+ }
37
+ if (NUMBER_GROUPS.has(group)) {
38
+ if (typeof value === "number") return value;
39
+ if (typeof value !== "string" || !NUMBER.test(value)) throw unsupported("native", path, value, "a unitless number");
40
+ const number = Number(value);
41
+ if (group === "opacity" && (number < 0 || number > 1)) throw unsupported("native", path, value, "an opacity between 0 and 1");
42
+ if (group === "zIndex" && !Number.isInteger(number)) throw unsupported("native", path, value, "an integer z-index");
43
+ return number;
44
+ }
45
+ if (typeof value === "string") return oklchAsHex(value.trim()) ?? value;
46
+ return value;
47
+ }
48
+ if (sourcePlatform === "react-native-system" && typeof value === "number") {
49
+ if (LENGTH_GROUPS.has(group)) return `${value}px`;
50
+ if (NUMBER_GROUPS.has(group)) return value;
51
+ throw unsupported("web", path, value, "a token with a known CSS unit");
52
+ }
53
+ return value;
54
+ }
55
+ /** The one adapter for base and mode values before a linked body is parsed by its destination. */
56
+ function prepareLinkedTokenBody({ path, body, source, config, destinationPlatform }) {
57
+ if (source.name === destinationPlatform) return body;
58
+ const remBase = source.name === "system-config" ? rootFontSize(source).px : void 0;
59
+ const convert = (value) => {
60
+ const literal = destinationPlatform === "react-native-system" ? nativeLiteral({
61
+ config,
62
+ value,
63
+ remBase
64
+ }) : value;
65
+ return typeof literal === "string" || typeof literal === "number" || typeof literal === "boolean" ? convertLinkedTokenValue({
66
+ path,
67
+ value: literal,
68
+ sourcePlatform: source.name,
69
+ destinationPlatform,
70
+ remBase
71
+ }) : literal;
72
+ };
73
+ const overrides = body.overrides;
74
+ return {
75
+ ...body,
76
+ value: convert(body.value),
77
+ ...overrides && typeof overrides === "object" && !Array.isArray(overrides) ? { overrides: Object.fromEntries(Object.entries(overrides).map(([key, raw]) => [key, raw && typeof raw === "object" && !Array.isArray(raw) ? {
78
+ ...raw,
79
+ value: convert(raw.value)
80
+ } : raw])) } : {}
81
+ };
82
+ }
83
+ /**
84
+ * A composed web value as a literal: an OKLCH colour as its CSS (which the conversion turns into a
85
+ * hex), arithmetic evaluated (a length in points where it comes to one), and a string's ref markers
86
+ * filled in. Refs and derived colours stay as they are, since the native theme resolves both. A value
87
+ * with no literal is returned unchanged for the native schema to refuse.
88
+ */
89
+ function nativeLiteral({ config, value, remBase }) {
90
+ const marked = typeof value === "string" && hasRefMarker(value);
91
+ const calc = isMultiply(value) || isDivide(value);
92
+ if (!marked && !calc && !isOklchColor(value)) return value;
93
+ const literal = literalValueOf(config, value);
94
+ if (literal === void 0) return value;
95
+ if (typeof literal !== "string") return literal;
96
+ const length = calc ? LENGTH.exec(literal) : null;
97
+ if (length && (length[2] === "px" || remBase !== void 0)) return Number(length[1]) * (length[2] === "rem" ? remBase : 1);
98
+ return literal;
99
+ }
100
+ /** An `oklch()` colour as the hex a native token holds, each channel clipped to sRGB as a browser
101
+ * paints it. Anything else answers `undefined`. */
102
+ function oklchAsHex(value) {
103
+ const match = OKLCH.exec(value);
104
+ if (!match) return void 0;
105
+ const [, l, lPercent, c, cPercent, h, a, aPercent] = match;
106
+ const lightness = Number(l) / (lPercent ? 100 : 1);
107
+ const chroma = Number(c) * (cPercent ? .004 : 1);
108
+ const hue = h === "none" ? 0 : Number(h) * Math.PI / 180;
109
+ const opacity = a === void 0 ? 1 : Number(a) / (aPercent ? 100 : 1);
110
+ const okA = chroma * Math.cos(hue);
111
+ const okB = chroma * Math.sin(hue);
112
+ const [lc, mc, sc] = [
113
+ lightness + .3963377774 * okA + .2158037573 * okB,
114
+ lightness - .1055613458 * okA - .0638541728 * okB,
115
+ lightness - .0894841775 * okA - 1.291485548 * okB
116
+ ].map((channel) => channel ** 3);
117
+ const linear = [
118
+ 4.0767416621 * lc - 3.3077115913 * mc + .2309699292 * sc,
119
+ -1.2684380046 * lc + 2.6097574011 * mc - .3413193965 * sc,
120
+ -.0041960863 * lc - .7034186147 * mc + 1.707614701 * sc
121
+ ];
122
+ const byte = (unit) => Math.round(Math.min(1, Math.max(0, unit)) * 255).toString(16).padStart(2, "0");
123
+ const encode = (channel) => {
124
+ const magnitude = Math.abs(channel);
125
+ const encoded = magnitude <= .0031308 ? 12.92 * magnitude : 1.055 * magnitude ** (1 / 2.4) - .055;
126
+ return Math.sign(channel) * encoded;
127
+ };
128
+ const hex = `#${linear.map((channel) => byte(encode(channel))).join("")}`;
129
+ return opacity < 1 ? `${hex}${byte(opacity)}` : hex;
130
+ }
131
+ function unsupported(destination, path, value, expected) {
132
+ return /* @__PURE__ */ new Error(`[uds] Linked ${destination} token "${path}" has value ${JSON.stringify(value)}; expected ${expected}.`);
133
+ }
134
+ //#endregion
135
+ export { convertLinkedTokenValue, prepareLinkedTokenBody };
@@ -1,17 +1,19 @@
1
1
  import { Ref } from "../../framework/utils/refs.js";
2
- import { DerivedColor } from "./color.js";
2
+ import { DerivedColor, OklchColor } from "./color.js";
3
3
  import { z } from "zod";
4
4
 
5
5
  //#region src/entities/system/shadow.d.ts
6
- /** A shadow layer's colour: a literal, a token ref, or a colour derived from one. */
7
- type ShadowColor = string | Ref | DerivedColor;
8
- /** One shadow spelled as its parts. The lengths are CSS lengths, as `box-shadow` takes them. */
6
+ /** A shadow layer's colour: a literal, a token ref, an OKLCH colour, or a colour derived from one. */
7
+ type ShadowColor = string | Ref | OklchColor | DerivedColor;
8
+ /** One shadow spelled as its parts. The lengths are CSS lengths, as `box-shadow` takes them. A shadow
9
+ * for `text-shadow` or `drop-shadow()`, which take no spread, leaves `spread` out, and one with no
10
+ * blur (`0 1px #000`) leaves `blur` out. */
9
11
  interface ShadowParts {
10
12
  readonly inset?: boolean;
11
13
  readonly offsetX: string;
12
14
  readonly offsetY: string;
13
- readonly blur: string;
14
- readonly spread: string;
15
+ readonly blur?: string;
16
+ readonly spread?: string;
15
17
  readonly color: ShadowColor;
16
18
  }
17
19
  /** A layer of the stack: a ref to a shadow token (all of its layers), or one shadow as parts. */
@@ -34,6 +36,6 @@ declare function shadow(...layers: readonly ShadowLayer[]): Shadow;
34
36
  * expression. It is passed in rather than imported because resolving a ref is the projection layer's
35
37
  * job and this module is the value's shape.
36
38
  */
37
- declare function renderShadow(value: Shadow, render: (part: ShadowLayer | ShadowColor) => string): string;
39
+ declare function renderShadow(value: Shadow, render: (part: unknown) => string): string;
38
40
  //#endregion
39
41
  export { Shadow, ShadowColor, ShadowLayer, ShadowParts, isShadow, renderShadow, shadow };
@@ -1,5 +1,5 @@
1
1
  import { isRef, refSchema } from "../../framework/utils/refs.js";
2
- import { derivedColor } from "./color.js";
2
+ import { derivedColor, oklchColor } from "./color.js";
3
3
  import { z } from "zod";
4
4
  //#region src/entities/system/shadow.ts
5
5
  /**
@@ -20,11 +20,12 @@ const shadowParts = z.strictObject({
20
20
  inset: z.boolean().optional(),
21
21
  offsetX: z.string(),
22
22
  offsetY: z.string(),
23
- blur: z.string(),
24
- spread: z.string(),
23
+ blur: z.string().optional(),
24
+ spread: z.string().optional(),
25
25
  color: z.union([
26
26
  z.string(),
27
27
  refSchema,
28
+ oklchColor,
28
29
  derivedColor
29
30
  ])
30
31
  });
@@ -64,10 +65,10 @@ function renderShadow(value, render) {
64
65
  const { inset, offsetX, offsetY, blur, spread, color } = layer;
65
66
  return [
66
67
  inset ? "inset" : void 0,
67
- offsetX,
68
- offsetY,
69
- blur,
70
- spread,
68
+ render(offsetX),
69
+ render(offsetY),
70
+ blur === void 0 ? spread === void 0 ? void 0 : "0" : render(blur),
71
+ spread === void 0 ? void 0 : render(spread),
71
72
  render(color)
72
73
  ].filter((part) => part !== void 0).join(" ");
73
74
  }).join(", ");
@@ -1,7 +1,7 @@
1
1
  import { SubEntityClass } from "../../framework/defineSubEntity.js";
2
2
  import { RegisteredModifiers, RegisteredStyleProps } from "../../framework/registered.js";
3
- import { Named } from "../../framework/named.js";
4
3
  import { ComposedValue } from "./composed-values.js";
4
+ import { Named } from "../../framework/named.js";
5
5
  import { z } from "zod";
6
6
 
7
7
  //#region src/entities/system/style-bag.d.ts
@@ -1,4 +1,4 @@
1
- import { isRef, splitRef } from "../../framework/utils/refs.js";
1
+ import { isRef, kindOf, pathOf, splitRef } from "../../framework/utils/refs.js";
2
2
  import { resolveValueType } from "../../css/value-type.js";
3
3
  import { resolveTokenValueUnder } from "../../framework/projections.js";
4
4
  import { matchEntities, searchEntities } from "../../framework/entity-search.js";
@@ -41,13 +41,56 @@ function bindingFor(config, path, label = path, source, under = null) {
41
41
  path: label,
42
42
  source,
43
43
  type,
44
- valueType: resolveValueType({
44
+ valueType: valueTypeOf({
45
+ config,
46
+ path,
45
47
  type,
46
48
  value
47
49
  }),
48
50
  value
49
51
  };
50
52
  }
53
+ /** A binding's visual type: its own declared type, else the first one declared along its ref chain,
54
+ * else what its literal (or, with none, its stored form at the chain's end) looks like. */
55
+ function valueTypeOf({ config, path, type, value }) {
56
+ const end = chainEnd(config, path);
57
+ const declared = type ?? end.type;
58
+ return resolveValueType({
59
+ type: declared,
60
+ value
61
+ }) ?? resolveValueType({
62
+ type: declared,
63
+ value: end.value
64
+ });
65
+ }
66
+ /** Along a token's ref chain: the first declared type, and the value stored at its end — the first
67
+ * token whose value is no ref to another. */
68
+ function chainEnd(config, path) {
69
+ const seen = /* @__PURE__ */ new Set();
70
+ let holder = config;
71
+ let at = path;
72
+ let type;
73
+ while (!seen.has(at)) {
74
+ seen.add(at);
75
+ const body = holder.resolve("token", at)?.toJSON();
76
+ type ??= body?.type;
77
+ if (!isRef(body?.value) || kindOf(body.value) !== "token") return {
78
+ type,
79
+ value: body?.value
80
+ };
81
+ const next = holder.configFor(body.value);
82
+ if (!next) return {
83
+ type,
84
+ value: void 0
85
+ };
86
+ holder = next;
87
+ at = pathOf(body.value);
88
+ }
89
+ return {
90
+ type,
91
+ value: void 0
92
+ };
93
+ }
51
94
  /**
52
95
  * What `value` binds to, or `undefined` when it is a literal.
53
96
  *
@@ -44,8 +44,9 @@ interface TokenValueRow {
44
44
  /** The rest of the path — `2.5` for `spacing/2.5`, `blue/3` for `spectrum/blue/3`. A path with no
45
45
  * slash is its own group and alias. */
46
46
  readonly alias: string;
47
- /** The custom property the stylesheet declares it as, spelled by the config that owns it. */
48
- readonly variable: string;
47
+ /** The custom property the stylesheet declares it as, spelled by the config that owns it. A token
48
+ * that declares none has none. */
49
+ readonly variable?: string;
49
50
  readonly state: TokenValueState;
50
51
  }
51
52
  /** The root font size of the page this config's sheet is for, as stated or the browser's own. A
@@ -1,8 +1,8 @@
1
- import { isRef, kindOf, pathOf, ref, splitRef } from "../../framework/utils/refs.js";
1
+ import { collectRefs, kindOf, pathOf, ref, splitRef } from "../../framework/utils/refs.js";
2
2
  import { parseRootFontSize } from "../../css/root-font-size.js";
3
3
  import { createSliceMemo } from "../../framework/memo.js";
4
4
  import { CSS_DEFAULTS, Settings } from "./Settings.js";
5
- import { cssVar, resolveTokenValue } from "../../framework/projections.js";
5
+ import { cssVar, declaresCssVariable, resolveTokenValue } from "../../framework/projections.js";
6
6
  //#region src/entities/system/token-values.ts
7
7
  /**
8
8
  * A token as a VALUE a module can compute with.
@@ -78,7 +78,7 @@ const tokenValueRows = createSliceMemo({
78
78
  path,
79
79
  group: slash > 0 ? path.slice(0, slash) : path,
80
80
  alias: slash > 0 ? path.slice(slash + 1) : path,
81
- variable: cssVar(owner, "token", path),
81
+ ...declaresCssVariable(owner, path) ? { variable: cssVar(owner, "token", path) } : {},
82
82
  state: stateOf({
83
83
  config,
84
84
  path,
@@ -137,12 +137,12 @@ function modeDecides(config, path, seen = /* @__PURE__ */ new Set()) {
137
137
  const body = config.resolve("token", path)?.toJSON();
138
138
  if (!body) return false;
139
139
  if (body.overrides && Object.keys(body.overrides).length > 0) return true;
140
- const { value } = body;
141
- if (!isRef(value) || kindOf(value) !== "token") return false;
142
- if (seen.has(value.__ref)) return false;
143
- seen.add(value.__ref);
144
- const source = config.configFor(value);
145
- return source ? modeDecides(source, pathOf(value), seen) : false;
140
+ return collectRefs(body.value).some((target) => {
141
+ if (kindOf(target) !== "token" || seen.has(target)) return false;
142
+ seen.add(target);
143
+ const source = config.configFor({ __ref: target });
144
+ return source ? modeDecides(source, pathOf(target), seen) : false;
145
+ });
146
146
  }
147
147
  /** The reader names, which is what the build recognizes on a `Token.ref(…)`. */
148
148
  const TOKEN_READERS = [