@yahoo/uds-create-config 1.1.1

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 (130) hide show
  1. package/dist/AssetGroup.d.ts +77 -0
  2. package/dist/AssetGroup.js +125 -0
  3. package/dist/Component.d.ts +308 -0
  4. package/dist/Component.js +896 -0
  5. package/dist/ComponentGroup.d.ts +20 -0
  6. package/dist/ComponentGroup.js +46 -0
  7. package/dist/CompositeStyle.d.ts +27 -0
  8. package/dist/CompositeStyle.js +52 -0
  9. package/dist/Config.d.ts +423 -0
  10. package/dist/Config.js +1429 -0
  11. package/dist/Mode.d.ts +41 -0
  12. package/dist/Mode.js +81 -0
  13. package/dist/Modifier.d.ts +51 -0
  14. package/dist/Modifier.js +97 -0
  15. package/dist/MotionDef.d.ts +49 -0
  16. package/dist/MotionDef.js +97 -0
  17. package/dist/Props.d.ts +317 -0
  18. package/dist/Props.js +35 -0
  19. package/dist/Provider.d.ts +20 -0
  20. package/dist/Provider.js +14 -0
  21. package/dist/StyleProp.d.ts +112 -0
  22. package/dist/StyleProp.js +197 -0
  23. package/dist/Token.d.ts +55 -0
  24. package/dist/Token.js +112 -0
  25. package/dist/TokenGroup.d.ts +32 -0
  26. package/dist/TokenGroup.js +68 -0
  27. package/dist/asset-kind.d.ts +55 -0
  28. package/dist/asset-kind.js +29 -0
  29. package/dist/brands.d.ts +30 -0
  30. package/dist/brands.js +20 -0
  31. package/dist/captureCallerPath.d.ts +48 -0
  32. package/dist/captureCallerPath.js +95 -0
  33. package/dist/colorExpressions.d.ts +131 -0
  34. package/dist/colorExpressions.js +148 -0
  35. package/dist/defineAssetGroup.d.ts +166 -0
  36. package/dist/defineAssetGroup.js +264 -0
  37. package/dist/defineProvider.d.ts +29 -0
  38. package/dist/defineProvider.js +60 -0
  39. package/dist/element-marker.d.ts +54 -0
  40. package/dist/element-marker.js +113 -0
  41. package/dist/entity-utils.d.ts +56 -0
  42. package/dist/entity-utils.js +105 -0
  43. package/dist/factories.d.ts +707 -0
  44. package/dist/factories.js +393 -0
  45. package/dist/foreign-component-name.d.ts +21 -0
  46. package/dist/foreign-component-name.js +42 -0
  47. package/dist/index.d.ts +32 -0
  48. package/dist/index.js +28 -0
  49. package/dist/interpolate.d.ts +20 -0
  50. package/dist/interpolate.js +10 -0
  51. package/dist/jsx/__fixtures__/cross-component-preview.d.ts +3 -0
  52. package/dist/jsx/__fixtures__/cross-component-preview.js +15 -0
  53. package/dist/jsx/jsx-dev-runtime.d.ts +15 -0
  54. package/dist/jsx/jsx-dev-runtime.js +11 -0
  55. package/dist/jsx/jsx-runtime.d.ts +48 -0
  56. package/dist/jsx/jsx-runtime.js +305 -0
  57. package/dist/markers.d.ts +217 -0
  58. package/dist/markers.js +67 -0
  59. package/dist/refs.d.ts +158 -0
  60. package/dist/refs.js +104 -0
  61. package/dist/renderer/RendererErrorBoundary.d.ts +26 -0
  62. package/dist/renderer/RendererErrorBoundary.js +30 -0
  63. package/dist/renderer/UdsRenderer.d.ts +80 -0
  64. package/dist/renderer/UdsRenderer.js +33 -0
  65. package/dist/renderer/assetRenderable.d.ts +13 -0
  66. package/dist/renderer/assetRenderable.js +13 -0
  67. package/dist/renderer/index.d.ts +14 -0
  68. package/dist/renderer/index.js +14 -0
  69. package/dist/renderer/makeRegistry.d.ts +34 -0
  70. package/dist/renderer/makeRegistry.js +52 -0
  71. package/dist/renderer/makeUdsRenderer.d.ts +29 -0
  72. package/dist/renderer/makeUdsRenderer.js +35 -0
  73. package/dist/renderer/primitives/FragmentRenderer.d.ts +12 -0
  74. package/dist/renderer/primitives/FragmentRenderer.js +11 -0
  75. package/dist/renderer/primitives/SlotRenderer.d.ts +21 -0
  76. package/dist/renderer/primitives/SlotRenderer.js +20 -0
  77. package/dist/renderer/wrapRegistry.d.ts +64 -0
  78. package/dist/renderer/wrapRegistry.js +39 -0
  79. package/dist/renderer/wrappers/component-slots.d.ts +41 -0
  80. package/dist/renderer/wrappers/component-slots.js +66 -0
  81. package/dist/renderer/wrappers/event-bridge.d.ts +20 -0
  82. package/dist/renderer/wrappers/event-bridge.js +78 -0
  83. package/dist/renderer/wrappers/hex-normalize.d.ts +14 -0
  84. package/dist/renderer/wrappers/hex-normalize.js +35 -0
  85. package/dist/renderer/wrappers/html-aliases.d.ts +24 -0
  86. package/dist/renderer/wrappers/html-aliases.js +62 -0
  87. package/dist/renderer/wrappers/inline-styles.d.ts +16 -0
  88. package/dist/renderer/wrappers/inline-styles.js +104 -0
  89. package/dist/renderer/wrappers/slot-resolution.d.ts +25 -0
  90. package/dist/renderer/wrappers/slot-resolution.js +68 -0
  91. package/dist/renderer/wrappers/void-elements.d.ts +23 -0
  92. package/dist/renderer/wrappers/void-elements.js +28 -0
  93. package/dist/spec/apply-forced-modifiers.d.ts +19 -0
  94. package/dist/spec/apply-forced-modifiers.js +49 -0
  95. package/dist/spec/asset-jsx.d.ts +48 -0
  96. package/dist/spec/asset-jsx.js +48 -0
  97. package/dist/spec/collapse-text-labels.d.ts +44 -0
  98. package/dist/spec/collapse-text-labels.js +107 -0
  99. package/dist/spec/empty-node-slots.d.ts +51 -0
  100. package/dist/spec/empty-node-slots.js +141 -0
  101. package/dist/spec/index.d.ts +10 -0
  102. package/dist/spec/index.js +10 -0
  103. package/dist/spec/jsxToSpec.d.ts +48 -0
  104. package/dist/spec/jsxToSpec.js +506 -0
  105. package/dist/spec/layer-props.d.ts +52 -0
  106. package/dist/spec/layer-props.js +149 -0
  107. package/dist/spec/preview-controls.d.ts +44 -0
  108. package/dist/spec/preview-controls.js +139 -0
  109. package/dist/spec/slot-refs.d.ts +39 -0
  110. package/dist/spec/slot-refs.js +56 -0
  111. package/dist/spec/specToJsx.d.ts +35 -0
  112. package/dist/spec/specToJsx.js +127 -0
  113. package/dist/token-override-rows.d.ts +66 -0
  114. package/dist/token-override-rows.js +223 -0
  115. package/dist/tokenValueType.d.ts +34 -0
  116. package/dist/tokenValueType.js +138 -0
  117. package/dist/tsconfig.tsbuildinfo +1 -0
  118. package/dist/types/css-properties.d.ts +232 -0
  119. package/dist/types/css-properties.js +14 -0
  120. package/dist/types/css-property-keywords.d.ts +156 -0
  121. package/dist/types/css-property-keywords.js +616 -0
  122. package/dist/types/css-values.d.ts +63 -0
  123. package/dist/types/css-values.js +16 -0
  124. package/dist/types.d.ts +708 -0
  125. package/dist/types.js +12 -0
  126. package/dist/units.d.ts +14 -0
  127. package/dist/units.js +16 -0
  128. package/dist/utils/index.d.ts +4 -0
  129. package/dist/utils/index.js +4 -0
  130. package/package.json +81 -0
package/dist/Config.js ADDED
@@ -0,0 +1,1429 @@
1
+ import "./brands.js";
2
+ import "./entity-utils.js";
3
+ import { AssetGroup } from "./AssetGroup.js";
4
+ import { attachSourcePath } from "./captureCallerPath.js";
5
+ import { isAssetGroupRef, isModeRef, isTokenRef } from "./refs.js";
6
+ import { ASSET_GROUP_SLUG_KEY, readAssetGroupSlug } from "./defineAssetGroup.js";
7
+ import { FOREIGN_NAMESPACE } from "./foreign-component-name.js";
8
+ import { Component, assertDefinitionAcceptsPlaceable } from "./Component.js";
9
+ import { ComponentGroup } from "./ComponentGroup.js";
10
+ import { CompositeStyle } from "./CompositeStyle.js";
11
+ import { Mode } from "./Mode.js";
12
+ import { Modifier } from "./Modifier.js";
13
+ import { CssMotionDef, JsMotionDef } from "./MotionDef.js";
14
+ import { Provider } from "./Provider.js";
15
+ import { StyleProp } from "./StyleProp.js";
16
+ import { TokenGroup } from "./TokenGroup.js";
17
+ //#region src/Config.ts
18
+ /**
19
+ * `Config` — top-level container for a UDS design system.
20
+ *
21
+ * Authored data lives on bare properties (Maps for collections, scalars
22
+ * for prefix/preflight). Derived data lives under `.derived`, a memoized
23
+ * lazy getter rebuilt whenever a `register*` chain method runs. The
24
+ * single dot to `.derived` is the boundary between "stored" and
25
+ * "computed"; consumers can tell at a glance which side of the line
26
+ * they're reading.
27
+ */
28
+ /** Compile-time exhaustiveness backstop for the `dependentsOf`/`dependenciesOf`
29
+ * switches: once every kind is handled, `query` narrows to `never` here; a new,
30
+ * unhandled kind makes the call a type error at the `default` branch. */
31
+ function assertNever(value) {
32
+ throw new Error(`Unhandled dependency kind: ${JSON.stringify(value)}`);
33
+ }
34
+ var Config = class Config {
35
+ static SCHEMA_VERSION = 1;
36
+ prefix = "uds";
37
+ /**
38
+ * Registry namespace — a stable, `package.json`-style identifier
39
+ * (`uds`, `@acme/system`) that namespaces every component's
40
+ * `registryKey` as `<namespace>:<ComponentName>` (the spec `type`).
41
+ * Declared in `uds.config.ts` via `configure({ namespace })` so it's
42
+ * available at build time; the push pipeline validates it for
43
+ * uniqueness / ownership. `undefined` ⇒ components keep bare-name
44
+ * registry keys. See Linear doc "RFC: Registry-namespaced component
45
+ * types".
46
+ */
47
+ namespace;
48
+ preflight = true;
49
+ globalStyles = {};
50
+ rawCss = [];
51
+ designPrinciples = [];
52
+ buildOptions = {};
53
+ tokenGroups = /* @__PURE__ */ new Map();
54
+ styleProps = /* @__PURE__ */ new Map();
55
+ modifiers = /* @__PURE__ */ new Map();
56
+ modes = /* @__PURE__ */ new Map();
57
+ compositeStyles = /* @__PURE__ */ new Map();
58
+ components = /* @__PURE__ */ new Map();
59
+ componentGroups = /* @__PURE__ */ new Map();
60
+ assetGroups = /* @__PURE__ */ new Map();
61
+ providers = /* @__PURE__ */ new Map();
62
+ motion = /* @__PURE__ */ new Map();
63
+ #derived;
64
+ /**
65
+ * Set top-level config metadata in one call — the single scalar-setter,
66
+ * replacing the former `withPrefix` / `withPreflight` / `withName` /
67
+ * `withDesignPrinciples` / `withBuildOptions` chain. Partial: only
68
+ * provided keys are applied, so it can run once or be merged across
69
+ * calls. `globalStyles` stays on `defineGlobalStyles` — its callback
70
+ * form needs the derived token refs, so it doesn't fit a plain bag.
71
+ *
72
+ * - `namespace` — the registry namespace (`uds`, `@acme/system`) that
73
+ * namespaces every component's `registryKey` as `<namespace>:<Component>`.
74
+ * Declared in `uds.config.ts` (the `package.json`-`name` model) so it's
75
+ * available at build time; the push pipeline additionally validates it
76
+ * for uniqueness / ownership. Throws on a structurally invalid value
77
+ * (the `:` separator, whitespace, or empty).
78
+ * - `prefix` — class-name + CSS-variable prefix (default `uds`); invalidates
79
+ * derived. Pass `''` or `false` to opt out entirely — classes and vars
80
+ * emit unprefixed (`bg-primary`, `--color-brand`). `false` is sugar for
81
+ * `''`, normalized to the empty string here so the value stays a string.
82
+ * - `preflight` — include Tailwind's reset (default `true`).
83
+ * - `designPrinciples` — freeform strings surfaced in the AI prompt.
84
+ * - `buildOptions` — shallow-merged into existing build options.
85
+ */
86
+ configure(options) {
87
+ if (options.namespace !== void 0) this.namespace = validateNamespace(options.namespace);
88
+ if (options.prefix !== void 0) {
89
+ this.prefix = options.prefix === false ? "" : options.prefix;
90
+ this.#invalidate();
91
+ }
92
+ if (options.preflight !== void 0) this.preflight = options.preflight;
93
+ if (options.designPrinciples !== void 0) this.designPrinciples = [...options.designPrinciples];
94
+ if (options.buildOptions !== void 0) this.buildOptions = {
95
+ ...this.buildOptions,
96
+ ...options.buildOptions
97
+ };
98
+ return this;
99
+ }
100
+ /**
101
+ * Resolve a spec element `type` (or any component identifier) to its
102
+ * {@link Component}, namespace-aware and backwards-compatible.
103
+ *
104
+ * `components` is keyed by the bare `name`, so:
105
+ * - an exact match is tried first — a bare `'Badge'` resolves directly,
106
+ * keeping every existing bare-name lookup working;
107
+ * - otherwise a namespaced registry key (`'<namespace>:<name>'`, the spec
108
+ * `type`) resolves by its bare name **only when the namespace is this
109
+ * registry's own**. A key from a different registry (`'other:Badge'`)
110
+ * deliberately does not resolve, so specs from multiple registries can
111
+ * coexist without colliding on a shared bare name.
112
+ *
113
+ * Returns `undefined` for unknown types and foreign (`foreign:`) sentinels.
114
+ * This is the single resolution path consumers should use instead of
115
+ * `config.components.get(type)` when `type` may be a spec/registry key.
116
+ */
117
+ getComponent(type) {
118
+ const direct = this.components.get(type);
119
+ if (direct) return direct;
120
+ const { namespace, name } = parseRegistryKey(type);
121
+ if (namespace !== void 0 && namespace === this.namespace) return this.components.get(name);
122
+ }
123
+ /**
124
+ * The bare, human-readable component name for a spec `type` — strips this
125
+ * registry's `<namespace>:` prefix (`'uds:Text'` → `'Text'`) for display, and
126
+ * the `foreign:` sentinel third-party leaves carry (`'foreign:CopyIcon'` →
127
+ * `'CopyIcon'`) — that prefix marks "not a UDS component," not a peer system,
128
+ * so the bare name is what every display surface wants. A bare type, an
129
+ * unknown type, or a key from another *peer* registry (`@acme/system:Button`)
130
+ * is returned unchanged — there the prefix is meaningful disambiguation.
131
+ */
132
+ componentDisplayName(type) {
133
+ const { namespace, name } = parseRegistryKey(type);
134
+ if (namespace === "foreign") return name;
135
+ return namespace !== void 0 && namespace === this.namespace ? name : type;
136
+ }
137
+ /**
138
+ * Components whose catalog `metadata.label` matches `label` (case-insensitive)
139
+ * — the display-name lookup that complements `getComponent` (registry key).
140
+ * Returns an array because labels aren't unique: a collision-dodging export
141
+ * (`StudioListItem` with `label: 'ListItem'`) can share a label with another
142
+ * component's key, so callers must handle 0, 1, or many. Empty when no label
143
+ * matches.
144
+ */
145
+ getComponentsByLabel(label) {
146
+ const lower = label.toLowerCase();
147
+ const out = [];
148
+ for (const component of this.components.values()) if (component.metadata?.label?.toLowerCase() === lower) out.push(component);
149
+ return out;
150
+ }
151
+ /**
152
+ * Style props that emit a given CSS property — the reverse of
153
+ * `StyleProp.cssProperty`. A property can be served by more than one prop
154
+ * (e.g. `border-color` by `borderColor`, `divideColor`; `margin-top` by both
155
+ * the positive `marginTop` and the `negative` `offsetTop`). Match is exact on
156
+ * the CSS property name; a prop targeting several properties matches any.
157
+ *
158
+ * `negative` props are ordered LAST, so a caller taking the first match
159
+ * (`getStylePropsByCssProperty(css)[0]`) always gets the primary, positive
160
+ * prop — the negative variant is only the first result when it's the sole
161
+ * prop for that property. Registration order is preserved within each group.
162
+ *
163
+ * Pass `opts.negative` to filter explicitly: `{ negative: false }` for only
164
+ * positive props, `{ negative: true }` for only the negative variant(s) (e.g.
165
+ * the `offsetTop` behind `margin-top`). Omit it to get every match.
166
+ */
167
+ getStylePropsByCssProperty(cssProperty, opts) {
168
+ const out = [];
169
+ for (const styleProp of this.styleProps.values()) {
170
+ const css = styleProp.cssProperty;
171
+ if (!(Array.isArray(css) ? css.includes(cssProperty) : css === cssProperty)) continue;
172
+ if (opts?.negative !== void 0 && styleProp.negative !== opts.negative) continue;
173
+ out.push(styleProp);
174
+ }
175
+ return out.sort((a, b) => Number(a.negative) - Number(b.negative));
176
+ }
177
+ /**
178
+ * Resolve the `StyleProp` behind a component's JSX prop, following any
179
+ * re-alias. A component can expose a style prop under a different JSX name
180
+ * than the registry key — `Box`'s `backgroundColor` prop binds
181
+ * `styleProp('bg')` — so this maps the on-component name back to the
182
+ * registered `StyleProp`. Returns `undefined` if the component has no such
183
+ * style prop.
184
+ */
185
+ getComponentStyleProp(componentName, jsxProp) {
186
+ const component = this.getComponent(componentName);
187
+ if (!component) return void 0;
188
+ for (const info of component.derived.props) if (info.kind === "styleProp" && info.name === jsxProp) return this.styleProps.get(info.stylePropName);
189
+ }
190
+ /**
191
+ * Components that render `name` as one of their layers — the reverse of
192
+ * `Layer.renders`. Layer composition only; subcomponent parentage is a
193
+ * separate edge (`Component.derived.subcomponentOf`). The component slice of
194
+ * the public {@link Config.dependentsOf} read surface.
195
+ */
196
+ #componentDependents(name) {
197
+ const out = [];
198
+ for (const component of this.components.values()) {
199
+ if (component.name === name) continue;
200
+ for (const layer of component.layers.values()) {
201
+ const renders = layer.renders;
202
+ if (renders?.kind === "component" && renders.ref === name) {
203
+ out.push(component.name);
204
+ break;
205
+ }
206
+ }
207
+ }
208
+ return out;
209
+ }
210
+ /**
211
+ * Components that expose a composite style as a prop (bound via
212
+ * `composite('<name>')`) — e.g. which components offer an `elevation`
213
+ * dropdown. The composite-style slice of {@link Config.dependentsOf}.
214
+ */
215
+ #compositeStyleDependents(name) {
216
+ const out = [];
217
+ for (const component of this.components.values()) for (const info of component.derived.props) if (info.kind === "composite" && info.compositeName === name) {
218
+ out.push(component.name);
219
+ break;
220
+ }
221
+ return out;
222
+ }
223
+ /**
224
+ * Components that expose a given style prop. Matches on the registered
225
+ * style-prop key (`info.stylePropName`), not the on-component JSX name, so a
226
+ * component that re-aliases the prop under a different name (a
227
+ * `backgroundColor` prop bound to `styleProp('bg')`) is still found; `as`
228
+ * carries that on-component name when it differs from the registry key. The
229
+ * style-prop slice of {@link Config.dependentsOf} (which keeps only the
230
+ * component name).
231
+ */
232
+ #stylePropDependents(name) {
233
+ const out = [];
234
+ for (const component of this.components.values()) for (const info of component.derived.props) if (info.kind === "styleProp" && info.stylePropName === name) {
235
+ out.push({
236
+ component: component.name,
237
+ as: info.name
238
+ });
239
+ break;
240
+ }
241
+ return out;
242
+ }
243
+ /**
244
+ * Style props that draw their values from a given token group — the group's
245
+ * "defined in" relation: a style prop references a group through its `values`
246
+ * array (`bg` ← `tokenGroup('color')`). Only style props reference token
247
+ * groups today, so the result is style-prop names; empty for an unknown
248
+ * group. The token-group slice of {@link Config.dependentsOf}; the *member*
249
+ * usage ("used in") is the separate {@link Config.getTokenGroupUsage}.
250
+ */
251
+ #tokenGroupDependents(namespace) {
252
+ const group = this.tokenGroups.get(namespace);
253
+ return group ? [...group.derived.styleProperties] : [];
254
+ }
255
+ /**
256
+ * Components that reference a given token anywhere in their styling. Reads
257
+ * the structural `Component.derived.tokenRefs` (which captures both
258
+ * `token(...)` refs and shorthand style-prop values, across base / variants /
259
+ * compound), so the match is on token *identity*, not CSS-var or substring
260
+ * matching. `name` may be qualified (`color/brand`) or bare (`brand`) — a
261
+ * bare name matches the token part in any group. The component half of a
262
+ * token's referrers in {@link Config.dependentsOf}; the token half is
263
+ * {@link Config.#tokenTokenDependents}.
264
+ */
265
+ #tokenComponentDependents(name) {
266
+ const qualified = name.includes("/");
267
+ const out = [];
268
+ for (const component of this.components.values()) {
269
+ const refs = component.derived.tokenRefs;
270
+ if (qualified ? refs.includes(name) : refs.some((ref) => ref.slice(ref.indexOf("/") + 1) === name)) out.push(component.name);
271
+ }
272
+ return out;
273
+ }
274
+ /**
275
+ * Other tokens that reference a given token — the "which tokens alias or
276
+ * mode-override this one?" lookup. Walks each token's value + modifiers for
277
+ * `token()` refs (the same structural channel `tokenUsageStats` counts).
278
+ * Returns qualified token names; `name` may be qualified or bare. Excludes
279
+ * self. The token half of a token's referrers in {@link Config.dependentsOf}.
280
+ */
281
+ #tokenTokenDependents(name) {
282
+ const qualified = name.includes("/");
283
+ const out = [];
284
+ for (const [qualifiedName, token] of this.derived.tokens) {
285
+ if (qualifiedName === name) continue;
286
+ const refs = /* @__PURE__ */ new Set();
287
+ this.#collectStyleTokenRefs(token.value, refs, /* @__PURE__ */ new WeakSet());
288
+ if (token.modifiers) this.#collectStyleTokenRefs(token.modifiers, refs, /* @__PURE__ */ new WeakSet());
289
+ if (qualified ? refs.has(name) : [...refs].some((ref) => ref.slice(ref.indexOf("/") + 1) === name)) out.push(qualifiedName);
290
+ }
291
+ return out;
292
+ }
293
+ /**
294
+ * Every qualified token name referenced *anywhere* in the system — the
295
+ * "used somewhere" set. Three reference channels, so a token reached only
296
+ * through one of them (e.g. a raw spectrum step a semantic token aliases,
297
+ * never touched by a component directly) still counts as used:
298
+ * - **components** — the structural `tokenRefs` union;
299
+ * - **token → token** — a token's value or a mode-override referencing
300
+ * another via `token()` (aliases, `_dark` swaps);
301
+ * - **composite styles** — `token()` refs and shorthand values in each bag.
302
+ */
303
+ #referencedTokenNames() {
304
+ const refs = /* @__PURE__ */ new Set();
305
+ for (const component of this.components.values()) for (const ref of component.derived.tokenRefs) refs.add(ref);
306
+ for (const token of this.derived.tokens.values()) {
307
+ this.#collectStyleTokenRefs(token.value, refs, /* @__PURE__ */ new WeakSet());
308
+ if (token.modifiers) this.#collectStyleTokenRefs(token.modifiers, refs, /* @__PURE__ */ new WeakSet());
309
+ }
310
+ for (const composite of this.compositeStyles.values()) for (const bag of composite.styles.values()) this.#collectStyleTokenRefs(bag, refs, /* @__PURE__ */ new WeakSet());
311
+ return refs;
312
+ }
313
+ /**
314
+ * Walk an arbitrary value (a token value, modifier map, or composite-style
315
+ * bag), collecting every token it references into `refs`: structured
316
+ * `token()` refs by identity (including ones nested in a color expression),
317
+ * and bare style-prop shorthand values resolved through the registry.
318
+ */
319
+ #collectStyleTokenRefs(node, refs, seen) {
320
+ if (node === null || typeof node !== "object") return;
321
+ if (isTokenRef(node)) {
322
+ refs.add(node.ref);
323
+ return;
324
+ }
325
+ if (seen.has(node)) return;
326
+ seen.add(node);
327
+ if (Array.isArray(node)) {
328
+ for (const item of node) this.#collectStyleTokenRefs(item, refs, seen);
329
+ return;
330
+ }
331
+ for (const [key, value] of Object.entries(node)) {
332
+ if (key.startsWith("__")) continue;
333
+ if (typeof value === "string") {
334
+ const ref = this.#resolveStylePropValueToken(key, value);
335
+ if (ref) refs.add(ref);
336
+ } else this.#collectStyleTokenRefs(value, refs, seen);
337
+ }
338
+ }
339
+ /**
340
+ * Aggregate token-usage stats across the whole registry — how many tokens
341
+ * are referenced *somewhere* (components, other tokens, or composite styles;
342
+ * see {@link Config.#referencedTokenNames}) vs not. `usagePercent` is
343
+ * rounded; `0` total tokens reports `0%`.
344
+ */
345
+ tokenUsageStats() {
346
+ const referenced = this.#referencedTokenNames();
347
+ const tokens = [...this.derived.tokens.keys()];
348
+ const total = tokens.length;
349
+ const used = tokens.filter((name) => referenced.has(name)).length;
350
+ const usagePercent = total === 0 ? 0 : Math.round(used / total * 100);
351
+ return {
352
+ total,
353
+ used,
354
+ unused: total - used,
355
+ usagePercent
356
+ };
357
+ }
358
+ /**
359
+ * Token values ranked by how often they appear across component base styles —
360
+ * the aggregate "what's load-bearing?" companion to `dependentsOf('token')`.
361
+ * `opts.property` narrows to one CSS property. Descends into `_<modifier>`
362
+ * sub-objects so a value used only in a hover/dark state still counts.
363
+ */
364
+ tokenValueUsage(opts) {
365
+ const property = opts?.property;
366
+ const counts = /* @__PURE__ */ new Map();
367
+ const collect = (styles) => {
368
+ for (const [prop, value] of Object.entries(styles)) if (typeof value === "string") {
369
+ if (property && prop.toLowerCase() !== property.toLowerCase()) continue;
370
+ const key = `${prop}:${value}`;
371
+ const existing = counts.get(key);
372
+ if (existing) existing.count++;
373
+ else counts.set(key, {
374
+ count: 1,
375
+ property: prop
376
+ });
377
+ } else if (isStyleRecord(value) && prop.startsWith("_")) collect(value);
378
+ };
379
+ for (const component of this.components.values()) {
380
+ if (!component.base) continue;
381
+ for (const styles of Object.values(component.base)) if (isStyleRecord(styles)) collect(styles);
382
+ }
383
+ return [...counts.entries()].map(([key, data]) => ({
384
+ value: key.slice(key.indexOf(":") + 1),
385
+ count: data.count,
386
+ property: data.property
387
+ })).sort((a, b) => b.count - a.count);
388
+ }
389
+ /**
390
+ * The reverse-dependency read surface — "what references this entity, and
391
+ * would break if I delete or rename it?" — uniform across every kind. One
392
+ * public entry point: callers dispatch by `kind` and get a single tagged
393
+ * list (the affected entities, each with its own kind), never a per-kind
394
+ * bespoke shape. The relation is always *references to the entity*; the
395
+ * per-kind walks behind it are private slices.
396
+ *
397
+ * A `token` resolves to BOTH the components that reference it AND the tokens
398
+ * that alias or mode-override it — a token's full referrer set, not split
399
+ * across two methods. A `modifier` flattens its components, composite styles,
400
+ * and token-group namespaces into the one tagged list. A token group's other
401
+ * relation — what actually *uses* its member tokens — is the separate
402
+ * {@link Config.getTokenGroupUsage} (only containers have it); ranking every
403
+ * entity of a kind at once is {@link Config.componentDependentCounts} & co.
404
+ */
405
+ dependentsOf(query) {
406
+ switch (query.kind) {
407
+ case "component": return this.#componentDependents(query.name).map((name) => ({
408
+ kind: "component",
409
+ name
410
+ }));
411
+ case "compositeStyle": return this.#compositeStyleDependents(query.name).map((name) => ({
412
+ kind: "component",
413
+ name
414
+ }));
415
+ case "styleProp":
416
+ if (query.value === void 0) return this.#stylePropDependents(query.name).map((d) => ({
417
+ kind: "component",
418
+ name: d.component
419
+ }));
420
+ return this.#stylePropKeywordDependents(query.name, query.value).map((name) => ({
421
+ kind: "component",
422
+ name
423
+ }));
424
+ case "token": return [...this.#tokenComponentDependents(query.name).map((name) => ({
425
+ kind: "component",
426
+ name
427
+ })), ...this.#tokenTokenDependents(query.name).map((name) => ({
428
+ kind: "token",
429
+ name
430
+ }))];
431
+ case "tokenGroup": return this.#tokenGroupDependents(query.name).map((name) => ({
432
+ kind: "styleProp",
433
+ name
434
+ }));
435
+ case "modifier": {
436
+ const m = this.#modifierDependents(query.name);
437
+ return [
438
+ ...m.components.map((name) => ({
439
+ kind: "component",
440
+ name
441
+ })),
442
+ ...m.compositeStyles.map((name) => ({
443
+ kind: "compositeStyle",
444
+ name
445
+ })),
446
+ ...m.tokenGroups.map((name) => ({
447
+ kind: "tokenGroup",
448
+ name
449
+ }))
450
+ ];
451
+ }
452
+ default: return assertNever(query);
453
+ }
454
+ }
455
+ /**
456
+ * The outbound mirror of {@link Config.dependentsOf} — "what does this entity
457
+ * *use*, and would I have to update if I delete those?". Same tagged
458
+ * `DependencyRef[]` shape, same `kind`-keyed exhaustiveness. A `component`
459
+ * reports the tokens it references, the keyword values it sets
460
+ * (`{ kind: 'styleProp', name, value }`), the composites and style props it
461
+ * binds, the components it renders as layers, and the modifiers it styles
462
+ * with; a `token` reports the tokens it aliases and the modifiers in its mode
463
+ * overrides; a `styleProp` reports the token groups it draws from. `modifier`
464
+ * and `tokenGroup` are leaves here — a selector and a container don't *use*
465
+ * other registry entities — so they report nothing.
466
+ */
467
+ dependenciesOf(query) {
468
+ switch (query.kind) {
469
+ case "component": return this.#componentDependencies(query.name);
470
+ case "token": return this.#tokenDependencies(query.name);
471
+ case "styleProp": {
472
+ const sp = this.styleProps.get(query.name);
473
+ if (!sp) return [];
474
+ return [...new Set(sp.consumedTokenGroups())].map((name) => ({
475
+ kind: "tokenGroup",
476
+ name
477
+ }));
478
+ }
479
+ case "compositeStyle": return this.#compositeStyleDependencies(query.name);
480
+ case "tokenGroup": {
481
+ const group = this.tokenGroups.get(query.name);
482
+ if (!group) return [];
483
+ return [...group.tokens.keys()].map((leaf) => ({
484
+ kind: "token",
485
+ name: `${query.name}/${leaf}`
486
+ }));
487
+ }
488
+ case "modifier": return [];
489
+ default: return assertNever(query);
490
+ }
491
+ }
492
+ /**
493
+ * Components that use a given style-prop *keyword* value — the "what breaks
494
+ * if I remove the `flex` value from `display`?" lookup, the value-narrowed
495
+ * slice of `dependentsOf({ kind: 'styleProp', name, value })`. Scoped to the
496
+ * `(styleProp, value)` pair (keywords aren't global — `col` on `flexDirection`
497
+ * is unrelated to `col` elsewhere), reading `derived.stylePropKeywords`.
498
+ */
499
+ #stylePropKeywordDependents(styleProp, value) {
500
+ const out = [];
501
+ for (const component of this.components.values()) if (component.derived.stylePropKeywords.some((kw) => kw.styleProp === styleProp && kw.value === value)) out.push(component.name);
502
+ return out;
503
+ }
504
+ /** Outbound edges of a component — its `dependenciesOf` slice: the tokens it
505
+ * references, the keyword values it sets, the composites + style props it
506
+ * binds, the components it renders as layers, and the modifiers it styles
507
+ * with. Deduped across those sources. */
508
+ #componentDependencies(name) {
509
+ const component = this.getComponent(name);
510
+ if (!component) return [];
511
+ const out = [];
512
+ const d = component.derived;
513
+ for (const ref of d.tokenRefs) out.push({
514
+ kind: "token",
515
+ name: ref
516
+ });
517
+ for (const kw of d.stylePropKeywords) out.push({
518
+ kind: "styleProp",
519
+ name: kw.styleProp,
520
+ value: kw.value
521
+ });
522
+ for (const info of d.props) if (info.kind === "composite") out.push({
523
+ kind: "compositeStyle",
524
+ name: info.compositeName
525
+ });
526
+ else if (info.kind === "styleProp") out.push({
527
+ kind: "styleProp",
528
+ name: info.stylePropName
529
+ });
530
+ for (const layer of component.layers.values()) {
531
+ const renders = layer.renders;
532
+ if (renders?.kind === "component" && renders.ref !== component.name) out.push({
533
+ kind: "component",
534
+ name: renders.ref
535
+ });
536
+ }
537
+ const mods = /* @__PURE__ */ new Set();
538
+ if (component.base) this.#collectModifierKeys(component.base, mods);
539
+ for (const binding of Object.values(component.props)) this.#collectModifierKeys(binding, mods);
540
+ for (const entry of component.compoundProps ?? []) this.#collectModifierKeys(entry.layers, mods);
541
+ for (const mod of mods) out.push({
542
+ kind: "modifier",
543
+ name: mod
544
+ });
545
+ return this.#dedupeRefs(out);
546
+ }
547
+ /** Outbound edges of a token — the tokens it aliases (value + mode overrides)
548
+ * and the modifiers carrying those overrides. `name` must be qualified. */
549
+ #tokenDependencies(name) {
550
+ const token = this.derived.tokens.get(name);
551
+ if (!token) return [];
552
+ const out = [];
553
+ const refs = /* @__PURE__ */ new Set();
554
+ this.#collectStyleTokenRefs(token.value, refs, /* @__PURE__ */ new WeakSet());
555
+ if (token.modifiers) this.#collectStyleTokenRefs(token.modifiers, refs, /* @__PURE__ */ new WeakSet());
556
+ for (const ref of refs) if (ref !== name) out.push({
557
+ kind: "token",
558
+ name: ref
559
+ });
560
+ for (const key of Object.keys(token.modifiers ?? {})) if (this.modifiers.has(key)) out.push({
561
+ kind: "modifier",
562
+ name: key
563
+ });
564
+ return out;
565
+ }
566
+ /** Outbound edges of a composite style — the tokens and modifiers across its
567
+ * variant style bags. */
568
+ #compositeStyleDependencies(name) {
569
+ const cs = this.compositeStyles.get(name);
570
+ if (!cs) return [];
571
+ const refs = /* @__PURE__ */ new Set();
572
+ const mods = /* @__PURE__ */ new Set();
573
+ for (const bag of cs.styles.values()) {
574
+ this.#collectStyleTokenRefs(bag, refs, /* @__PURE__ */ new WeakSet());
575
+ this.#collectModifierKeys(bag, mods);
576
+ }
577
+ return [...[...refs].map((name) => ({
578
+ kind: "token",
579
+ name
580
+ })), ...[...mods].map((name) => ({
581
+ kind: "modifier",
582
+ name
583
+ }))];
584
+ }
585
+ /** Collect every registered single-underscore modifier key reachable in a
586
+ * styling node (skipping `__`-internals), into `into`. */
587
+ #collectModifierKeys(node, into, seen = /* @__PURE__ */ new WeakSet()) {
588
+ if (node === null || typeof node !== "object") return;
589
+ if (seen.has(node)) return;
590
+ seen.add(node);
591
+ for (const [key, value] of Object.entries(node)) {
592
+ if (key.startsWith("__")) continue;
593
+ if (key.startsWith("_") && this.modifiers.has(key)) into.add(key);
594
+ this.#collectModifierKeys(value, into, seen);
595
+ }
596
+ }
597
+ /** Dedupe a `DependencyRef[]` by `(kind, name, value)`. */
598
+ #dedupeRefs(refs) {
599
+ const seen = /* @__PURE__ */ new Set();
600
+ const out = [];
601
+ for (const ref of refs) {
602
+ const key = `${ref.kind} ${ref.name} ${ref.value ?? ""}`;
603
+ if (seen.has(key)) continue;
604
+ seen.add(key);
605
+ out.push(ref);
606
+ }
607
+ return out;
608
+ }
609
+ /**
610
+ * Everything that references a modifier (`_hover`, `_dataStateOpen`, `_dark`)
611
+ * in its own styling. Walks all the places a `_<modifier>` key can appear:
612
+ * - **components**: `base`, each variant's per-layer styles, and
613
+ * `compoundProps` layer overrides;
614
+ * - **compositeStyles**: each variant bag;
615
+ * - **tokenGroups**: tokens carrying a `_<modifier>` mode override (e.g.
616
+ * `{ value, _dark }`) — grouped by namespace since a mode modifier touches
617
+ * many tokens at once.
618
+ * Matches single-underscore modifier keys only (skips `__kind` etc.). The
619
+ * modifier slice of {@link Config.dependentsOf}, which flattens the three
620
+ * buckets into one tagged list.
621
+ */
622
+ #modifierDependents(modifier) {
623
+ const usesModifier = (node, seen = /* @__PURE__ */ new WeakSet()) => {
624
+ if (node === null || typeof node !== "object") return false;
625
+ if (seen.has(node)) return false;
626
+ seen.add(node);
627
+ for (const [key, value] of Object.entries(node)) {
628
+ if (key === modifier) return true;
629
+ if (key.startsWith("__")) continue;
630
+ if (usesModifier(value, seen)) return true;
631
+ }
632
+ return false;
633
+ };
634
+ const components = [];
635
+ for (const component of this.components.values()) {
636
+ const inBase = component.base ? usesModifier(component.base) : false;
637
+ const inProps = !inBase && Object.values(component.props).some((binding) => usesModifier(binding));
638
+ const inCompound = !inBase && !inProps && (component.compoundProps?.some((e) => usesModifier(e.layers)) ?? false);
639
+ if (inBase || inProps || inCompound) components.push(component.name);
640
+ }
641
+ const compositeStyles = [];
642
+ for (const cs of this.compositeStyles.values()) for (const bag of cs.styles.values()) if (usesModifier(bag)) {
643
+ compositeStyles.push(cs.name);
644
+ break;
645
+ }
646
+ const tokenGroups = [];
647
+ for (const group of this.tokenGroups.values()) for (const token of group.tokens.values()) if (token.modifiers && Object.hasOwn(token.modifiers, modifier)) {
648
+ tokenGroups.push(group.namespace);
649
+ break;
650
+ }
651
+ return {
652
+ components,
653
+ compositeStyles,
654
+ tokenGroups
655
+ };
656
+ }
657
+ /**
658
+ * The aggregate sibling of {@link Config.dependentsOf} — for *every* entity of
659
+ * a kind at once, how many dependents each has (`Map<name, count>`, ranked by
660
+ * `uds_analyze_usage`). A single entity's count is just
661
+ * `dependentsOf(query).length`; this exists for the all-at-once case, where
662
+ * calling that per entity would re-walk the component set N times
663
+ * (O(N × components)) — each private slice walks once (O(components)) and
664
+ * tallies as it goes, kept consistent with `dependentsOf` (a dependent
665
+ * counted once per entity, self-references excluded; the cross-check tests
666
+ * assert the equivalence against the live config).
667
+ */
668
+ dependentCounts(kind) {
669
+ switch (kind) {
670
+ case "component": return this.#componentDependentCounts();
671
+ case "compositeStyle": return this.#compositeStyleDependentCounts();
672
+ case "styleProp": return this.#stylePropDependentCounts();
673
+ case "tokenGroup": return this.#tokenGroupDependentCounts();
674
+ case "modifier": return this.#modifierDependentCounts();
675
+ case "token": return this.#tokenDependentCounts();
676
+ default: return assertNever(kind);
677
+ }
678
+ }
679
+ #componentDependentCounts() {
680
+ const counts = /* @__PURE__ */ new Map();
681
+ for (const component of this.components.values()) {
682
+ const refs = /* @__PURE__ */ new Set();
683
+ for (const layer of component.layers.values()) {
684
+ const renders = layer.renders;
685
+ if (renders?.kind === "component" && renders.ref !== component.name) refs.add(renders.ref);
686
+ }
687
+ for (const ref of refs) counts.set(ref, (counts.get(ref) ?? 0) + 1);
688
+ }
689
+ return counts;
690
+ }
691
+ #compositeStyleDependentCounts() {
692
+ const counts = /* @__PURE__ */ new Map();
693
+ for (const component of this.components.values()) {
694
+ const names = /* @__PURE__ */ new Set();
695
+ for (const info of component.derived.props) if (info.kind === "composite") names.add(info.compositeName);
696
+ for (const name of names) counts.set(name, (counts.get(name) ?? 0) + 1);
697
+ }
698
+ return counts;
699
+ }
700
+ #stylePropDependentCounts() {
701
+ const counts = /* @__PURE__ */ new Map();
702
+ for (const component of this.components.values()) {
703
+ const names = /* @__PURE__ */ new Set();
704
+ for (const info of component.derived.props) if (info.kind === "styleProp") names.add(info.stylePropName);
705
+ for (const name of names) counts.set(name, (counts.get(name) ?? 0) + 1);
706
+ }
707
+ return counts;
708
+ }
709
+ /**
710
+ * Single-pass companion to `dependentsOf('tokenGroup', …)` — how many style props
711
+ * draw from each token-group namespace. Walks the style props once (the
712
+ * forward `consumedTokenGroups` direction) rather than re-deriving each
713
+ * group's consumer list, counting a prop once per group it references.
714
+ */
715
+ #tokenGroupDependentCounts() {
716
+ const counts = /* @__PURE__ */ new Map();
717
+ for (const styleProp of this.styleProps.values()) for (const namespace of new Set(styleProp.consumedTokenGroups())) counts.set(namespace, (counts.get(namespace) ?? 0) + 1);
718
+ return counts;
719
+ }
720
+ /**
721
+ * Single-pass companion to `dependentsOf('modifier', …)` — the total it sums per
722
+ * modifier (components + composite styles + token-group namespaces that
723
+ * reference it). Mirrors the modifier walk's traversal rules: gathers every
724
+ * single-underscore key (skipping `__`-prefixed internals, so a slot's
725
+ * `__accepts` child definitions never leak in), counts each source once per
726
+ * modifier, and tallies only registered modifiers.
727
+ */
728
+ #modifierDependentCounts() {
729
+ const counts = /* @__PURE__ */ new Map();
730
+ const bump = (key, into) => {
731
+ if (this.modifiers.has(key)) into.add(key);
732
+ };
733
+ const collect = (node, into, seen = /* @__PURE__ */ new WeakSet()) => {
734
+ if (node === null || typeof node !== "object") return;
735
+ if (seen.has(node)) return;
736
+ seen.add(node);
737
+ for (const [key, value] of Object.entries(node)) {
738
+ if (key.startsWith("__")) continue;
739
+ if (key.startsWith("_")) bump(key, into);
740
+ collect(value, into, seen);
741
+ }
742
+ };
743
+ const tally = (keys) => {
744
+ for (const key of keys) counts.set(key, (counts.get(key) ?? 0) + 1);
745
+ };
746
+ for (const component of this.components.values()) {
747
+ const keys = /* @__PURE__ */ new Set();
748
+ if (component.base) collect(component.base, keys);
749
+ for (const binding of Object.values(component.props)) collect(binding, keys);
750
+ for (const entry of component.compoundProps ?? []) collect(entry.layers, keys);
751
+ tally(keys);
752
+ }
753
+ for (const cs of this.compositeStyles.values()) {
754
+ const keys = /* @__PURE__ */ new Set();
755
+ for (const bag of cs.styles.values()) collect(bag, keys);
756
+ tally(keys);
757
+ }
758
+ for (const group of this.tokenGroups.values()) {
759
+ const keys = /* @__PURE__ */ new Set();
760
+ for (const token of group.tokens.values()) for (const key of Object.keys(token.modifiers ?? {})) bump(key, keys);
761
+ tally(keys);
762
+ }
763
+ return counts;
764
+ }
765
+ /** Single-pass `dependentCounts('token')` — referrers per token: each
766
+ * component counted once per token it references, plus each token that
767
+ * aliases/mode-overrides another (self excluded). Mirrors the union
768
+ * `dependentsOf({ kind: 'token' })` returns. */
769
+ #tokenDependentCounts() {
770
+ const counts = /* @__PURE__ */ new Map();
771
+ const bump = (name) => counts.set(name, (counts.get(name) ?? 0) + 1);
772
+ for (const component of this.components.values()) for (const ref of new Set(component.derived.tokenRefs)) bump(ref);
773
+ for (const [qualifiedName, token] of this.derived.tokens) {
774
+ const refs = /* @__PURE__ */ new Set();
775
+ this.#collectStyleTokenRefs(token.value, refs, /* @__PURE__ */ new WeakSet());
776
+ if (token.modifiers) this.#collectStyleTokenRefs(token.modifiers, refs, /* @__PURE__ */ new WeakSet());
777
+ for (const ref of refs) if (ref !== qualifiedName) bump(ref);
778
+ }
779
+ return counts;
780
+ }
781
+ /**
782
+ * A component's style-prop surface as a delta against a base set, so
783
+ * consumers can render "inherits Box (226); adds …; removes …" instead of
784
+ * re-emitting the full ~226-name list per component. The base is the
785
+ * `extendsFrom` parent's set when value-extending, else the union across the
786
+ * `primitives` group (the set every primitive shares). `baseSource` is the
787
+ * parent's name, or `undefined` when the base is the primitives union — every
788
+ * `extends:Box` primitive has an identical set today, so `adds`/`removes` are
789
+ * usually empty, which is the dedup the projection relies on.
790
+ */
791
+ stylePropSummary(component) {
792
+ const own = component.derived.stylePropNames;
793
+ const parent = component.extendsFrom ? this.components.get(component.extendsFrom) : void 0;
794
+ const baseSource = parent ? component.extendsFrom : void 0;
795
+ const base = parent ? parent.derived.stylePropNames : this.#primitivesStyleProps();
796
+ const baseSet = new Set(base);
797
+ const ownSet = new Set(own);
798
+ return {
799
+ own,
800
+ base,
801
+ baseSource,
802
+ adds: own.filter((n) => !baseSet.has(n)),
803
+ removes: baseSource ? base.filter((n) => !ownSet.has(n)) : []
804
+ };
805
+ }
806
+ /** Union of style-prop names across the `primitives` group — the comparison
807
+ * base for root components (no `extendsFrom`, e.g. `Box`). */
808
+ #primitivesStyleProps() {
809
+ const names = /* @__PURE__ */ new Set();
810
+ for (const component of this.components.values()) if (component.derived.componentGroup === "primitives") for (const n of component.derived.stylePropNames) names.add(n);
811
+ return [...names];
812
+ }
813
+ /**
814
+ * Author global CSS — a selector → declarations bag. The callback
815
+ * form receives the same 2D CSS-var ref table as `derived.cssVarRefs`
816
+ * (`tokens.bg.primary` resolves to a `var(--uds-bg-primary)` brand)
817
+ * so consumers can reach for token values without re-deriving them.
818
+ * Mirrors the old `@yahoo/uds-create-config` `defineGlobalStyles` chain.
819
+ */
820
+ defineGlobalStyles(input) {
821
+ this.globalStyles = typeof input === "function" ? input(this.derived.cssVarRefs) : input;
822
+ return this;
823
+ }
824
+ /**
825
+ * Include a raw CSS string verbatim in the generated stylesheet — for
826
+ * declarations the token/style-prop system can't express, most commonly
827
+ * `@font-face` blocks (the codegen pipeline emits none on its own) plus
828
+ * the `:root { --<prefix>-font-family-<id>: … }` definitions that font
829
+ * tokens reference. Accumulates across calls.
830
+ *
831
+ * Takes the CSS *content*, not a path, so `Config` stays free of Node
832
+ * `fs` and remains importable in the browser/runtime. Read the file in
833
+ * `uds.config.ts` (Node context) and pass the string:
834
+ *
835
+ * ```ts
836
+ * import { readFileSync } from 'node:fs';
837
+ * const fontsCss = readFileSync(new URL('./fonts.css', import.meta.url), 'utf8');
838
+ * export default defineConfig({ … }).includeCss(fontsCss);
839
+ * ```
840
+ *
841
+ * Injected after Tailwind compilation and the unused-var purge, so
842
+ * `@font-face` families and any custom properties it declares are never
843
+ * stripped as "unused".
844
+ */
845
+ includeCss(css) {
846
+ this.rawCss.push(css);
847
+ return this;
848
+ }
849
+ registerModes(modes) {
850
+ for (const [name, def] of Object.entries(modes)) this.modes.set(name, new Mode({
851
+ name,
852
+ definition: def
853
+ }));
854
+ return this;
855
+ }
856
+ registerModifiers(modifiers) {
857
+ for (const [name, def] of Object.entries(modifiers)) {
858
+ const key = name;
859
+ this.modifiers.set(key, new Modifier({
860
+ name: key,
861
+ definition: def,
862
+ compositeLookup: (ref) => this.#lookupCompositeSelector(ref),
863
+ modeLookup: (ref) => this.#lookupModeSelector(ref)
864
+ }));
865
+ }
866
+ return this;
867
+ }
868
+ registerTokenGroups(groups) {
869
+ for (const [namespace, def] of Object.entries(groups)) this.tokenGroups.set(namespace, new TokenGroup({
870
+ namespace,
871
+ prefixGetter: () => this.prefix,
872
+ definition: def,
873
+ tokenLookup: (ref) => this.derived.tokens.get(ref),
874
+ stylePropertiesLookup: (ns) => this.#stylePropertiesFor(ns)
875
+ }));
876
+ this.#invalidate();
877
+ return this;
878
+ }
879
+ registerStyleProps(props) {
880
+ for (const [name, def] of Object.entries(props)) this.styleProps.set(name, new StyleProp({
881
+ name,
882
+ definition: def,
883
+ tokenLookup: (groupName) => this.#tokensInGroup(groupName),
884
+ prefixGetter: () => this.prefix
885
+ }));
886
+ return this;
887
+ }
888
+ registerComposites(composites) {
889
+ for (const [name, def] of Object.entries(composites)) this.compositeStyles.set(name, new CompositeStyle({
890
+ name,
891
+ definition: def,
892
+ prefixGetter: () => this.prefix
893
+ }));
894
+ return this;
895
+ }
896
+ registerMotion(motion) {
897
+ for (const [name, def] of Object.entries(motion)) this.motion.set(name, def.runtime === "js" ? new JsMotionDef({
898
+ name,
899
+ definition: def
900
+ }) : new CssMotionDef({
901
+ name,
902
+ definition: def
903
+ }));
904
+ return this;
905
+ }
906
+ registerComponents(components) {
907
+ const parentOf = /* @__PURE__ */ new Map();
908
+ const childrenOf = /* @__PURE__ */ new Map();
909
+ const flat = new Map(Object.entries(components));
910
+ for (const [parentName, def] of Object.entries(components)) {
911
+ const subs = def.__subcomponents;
912
+ if (!subs) continue;
913
+ const childNames = [];
914
+ for (const [childName, child] of Object.entries(subs)) {
915
+ const existing = parentOf.get(childName);
916
+ if (existing !== void 0) throw new Error(`registerComponents: subcomponent "${childName}" registered under both "${existing}" and "${parentName}"`);
917
+ if (Object.hasOwn(components, childName)) throw new Error(`registerComponents: "${childName}" is registered both as a top-level component and as a subcomponent of "${parentName}" — pick one`);
918
+ flat.set(childName, child);
919
+ parentOf.set(childName, parentName);
920
+ childNames.push(childName);
921
+ }
922
+ childrenOf.set(parentName, childNames);
923
+ }
924
+ for (const [name, def] of flat) Object.defineProperty(def, "__componentName", {
925
+ enumerable: false,
926
+ configurable: true,
927
+ writable: true,
928
+ value: name
929
+ });
930
+ for (const [name, def] of flat) {
931
+ assertNameAvailable(this.components, name, this.namespace);
932
+ assertDefinitionAcceptsPlaceable(name, def);
933
+ this.components.set(name, new Component({
934
+ name,
935
+ definition: def,
936
+ subcomponentOf: parentOf.get(name),
937
+ subcomponents: childrenOf.get(name),
938
+ namespaceGetter: () => this.namespace,
939
+ prefixGetter: () => this.prefix,
940
+ componentResolver: (ref) => this.getComponent(ref),
941
+ resolveStylePropValueToken: (prop, value) => this.#resolveStylePropValueToken(prop, value),
942
+ isStyleProp: (propName) => this.styleProps.has(propName)
943
+ }));
944
+ }
945
+ return this;
946
+ }
947
+ /**
948
+ * Build a single component from its definition with this config's context
949
+ * getters wired — `prefixGetter`, `namespaceGetter`, `componentResolver` —
950
+ * and set it on the registry, replacing any existing entry of the same name.
951
+ *
952
+ * This is the ONE sanctioned way to put a constructed component into the
953
+ * config outside `registerComponents`. Constructing a `Component` by hand and
954
+ * calling `config.components.set(...)` silently drops those getters, so the
955
+ * component's `derived.classNames` fall back to an empty prefix/namespace and
956
+ * its anatomy CSS emits unprefixed (`:where(.button-root)`) — it renders
957
+ * unstyled while the runtime applies the prefixed class. Patch-apply code that
958
+ * rebuilds a component from a mutated definition (e.g.
959
+ * `applyComponentStyleUpdatePatch`) routes through here so the wiring can't be
960
+ * forgotten.
961
+ */
962
+ upsertComponent(name, definition, meta) {
963
+ this.components.set(name, new Component({
964
+ name,
965
+ definition,
966
+ componentGroup: meta?.componentGroup,
967
+ subcomponentOf: meta?.subcomponentOf,
968
+ subcomponents: meta?.subcomponents,
969
+ namespaceGetter: () => this.namespace,
970
+ prefixGetter: () => this.prefix,
971
+ componentResolver: (ref) => this.getComponent(ref),
972
+ resolveStylePropValueToken: (prop, value) => this.#resolveStylePropValueToken(prop, value),
973
+ isStyleProp: (propName) => this.styleProps.has(propName)
974
+ }));
975
+ return this;
976
+ }
977
+ /**
978
+ * Register one or more labeled bundles of components. Each group's
979
+ * `components` record is flattened into `this.components` (same
980
+ * surface as `registerComponents`), and every member's
981
+ * `Component.derived.componentGroup` carries the group key. The
982
+ * group entry itself lives on `this.componentGroups[key]` with a
983
+ * JSON-safe ref list (`components: readonly string[]`).
984
+ *
985
+ * Subcomponents declared on a grouped parent via
986
+ * `.subcomponents({...})` auto-register alongside their parent;
987
+ * they carry `subcomponentOf` instead of `componentGroup` (a
988
+ * subcomponent belongs to a parent, not a group).
989
+ *
990
+ * Component names are the identity (`<namespace>:<name>`, the spec
991
+ * `type`) and must be unique across the whole config — the same name
992
+ * under two groups, repeated in one group, or already registered by a
993
+ * prior `register*` call throws. Two components that should *display*
994
+ * the same use distinct names + `metadata.label`.
995
+ */
996
+ registerComponentGroups(groups) {
997
+ const groupOf = /* @__PURE__ */ new Map();
998
+ const parentOf = /* @__PURE__ */ new Map();
999
+ const childrenOf = /* @__PURE__ */ new Map();
1000
+ const flat = /* @__PURE__ */ new Map();
1001
+ const groupRecords = /* @__PURE__ */ new Map();
1002
+ for (const [groupKey, group] of Object.entries(groups)) {
1003
+ const componentNames = [];
1004
+ for (const [name, def] of Object.entries(group.components)) {
1005
+ if (this.components.has(name) || flat.has(name)) throw new Error(`registerComponentGroups: component "${name}" is already registered${groupOf.get(name) ? ` (group "${groupOf.get(name)}")` : ""} — component names must be unique within a registry. Rename one (e.g. "Studio${name}") and give it metadata.label "${name}" for a shared display name.`);
1006
+ flat.set(name, def);
1007
+ groupOf.set(name, groupKey);
1008
+ componentNames.push(name);
1009
+ }
1010
+ groupRecords.set(groupKey, {
1011
+ label: group.label,
1012
+ description: group.description,
1013
+ componentNames
1014
+ });
1015
+ }
1016
+ for (const group of Object.values(groups)) for (const [parentName, def] of Object.entries(group.components)) {
1017
+ const subs = def.__subcomponents;
1018
+ if (!subs) continue;
1019
+ const childNames = [];
1020
+ for (const [childName, child] of Object.entries(subs)) {
1021
+ const existingParent = parentOf.get(childName);
1022
+ if (existingParent !== void 0) throw new Error(`registerComponentGroups: subcomponent "${childName}" registered under both "${existingParent}" and "${parentName}"`);
1023
+ if (groupOf.has(childName)) throw new Error(`registerComponentGroups: "${childName}" is registered both as a top-level component and as a subcomponent of "${parentName}" — pick one`);
1024
+ flat.set(childName, child);
1025
+ parentOf.set(childName, parentName);
1026
+ childNames.push(childName);
1027
+ }
1028
+ childrenOf.set(parentName, childNames);
1029
+ }
1030
+ for (const [name, def] of flat) Object.defineProperty(def, "__componentName", {
1031
+ enumerable: false,
1032
+ configurable: true,
1033
+ writable: true,
1034
+ value: name
1035
+ });
1036
+ for (const [name, def] of flat) {
1037
+ assertNameAvailable(this.components, name, this.namespace);
1038
+ assertDefinitionAcceptsPlaceable(name, def);
1039
+ this.components.set(name, new Component({
1040
+ name,
1041
+ definition: def,
1042
+ componentGroup: groupOf.get(name),
1043
+ subcomponentOf: parentOf.get(name),
1044
+ subcomponents: childrenOf.get(name),
1045
+ namespaceGetter: () => this.namespace,
1046
+ prefixGetter: () => this.prefix,
1047
+ componentResolver: (ref) => this.getComponent(ref),
1048
+ resolveStylePropValueToken: (prop, value) => this.#resolveStylePropValueToken(prop, value),
1049
+ isStyleProp: (propName) => this.styleProps.has(propName)
1050
+ }));
1051
+ }
1052
+ for (const [groupKey, record] of groupRecords) this.componentGroups.set(groupKey, new ComponentGroup({
1053
+ name: groupKey,
1054
+ label: record.label,
1055
+ description: record.description,
1056
+ components: record.componentNames
1057
+ }));
1058
+ return this;
1059
+ }
1060
+ /**
1061
+ * Register one or more asset groups. The record key IS the slug —
1062
+ * stamped onto the group at registration (late-bound, like
1063
+ * `registerComponentGroups` deriving a group's name from its key and
1064
+ * `Component.registryKey` reading the namespace getter). Each member
1065
+ * then resolves to `${namespace}:asset:${slug}/${assetName}` via
1066
+ * `AssetGroup.assetType()`.
1067
+ *
1068
+ * Trade-off (accepted, same as `registerComponentGroups`): the
1069
+ * `{ icons }` shorthand ties the slug to the variable name — write
1070
+ * the key out explicitly (`{ icons: phosphorIcons }`) whenever it
1071
+ * shouldn't track the variable.
1072
+ */
1073
+ registerAssetGroups(groups) {
1074
+ for (const [slug, def] of Object.entries(groups)) {
1075
+ if (!isAssetGroupRef(def)) {
1076
+ const looksLikeBuilder = typeof def.config === "function";
1077
+ throw new Error(looksLikeBuilder ? `registerAssetGroups: group "${slug}" is missing its .config({...}) call — defineAssetGroup(members).config({ sizes }) is a complete icon registration.` : `registerAssetGroups: value for "${slug}" is not an asset group — author it with defineAssetGroup(members).config({...}).`);
1078
+ }
1079
+ if (!/^[A-Za-z][A-Za-z0-9]*(?:[-_][A-Za-z0-9]+)*$/.test(slug)) throw new Error(`registerAssetGroups: slug "${slug}" is invalid — slugs become the asset-id group segment (alphanumeric segments separated by single "-" or "_").`);
1080
+ if (this.assetGroups.has(slug)) throw new Error(`registerAssetGroups: asset group "${slug}" is already registered — slugs must be unique within a config.`);
1081
+ const priorSlug = readAssetGroupSlug(def);
1082
+ if (priorSlug !== void 0 && priorSlug !== slug) throw new Error(`registerAssetGroups: this group is already registered as "${priorSlug}" — a group has one identity; re-export it instead of registering it twice.`);
1083
+ def[ASSET_GROUP_SLUG_KEY] = slug;
1084
+ this.assetGroups.set(slug, new AssetGroup({
1085
+ name: slug,
1086
+ label: def.label ?? titleCaseSlug(slug),
1087
+ version: def.version,
1088
+ description: def.description,
1089
+ assetKind: def.assetKind,
1090
+ assetNames: Object.keys(def.members),
1091
+ sizes: def.assetKind === "icon" ? def.sizes : void 0,
1092
+ variants: def.assetKind === "icon" ? def.variants : void 0,
1093
+ component: def.assetKind === "icon" ? def.component : void 0,
1094
+ members: def.members,
1095
+ memberMetadata: def.assetKind === "icon" ? def.memberMetadata : void 0,
1096
+ namespaceGetter: () => this.namespace
1097
+ }));
1098
+ }
1099
+ return this;
1100
+ }
1101
+ registerProviders(providers) {
1102
+ for (const [name, ProviderFC] of Object.entries(providers)) {
1103
+ ProviderFC.displayName = name;
1104
+ const marker = ProviderFC.__udsProvider;
1105
+ marker.name = name;
1106
+ this.providers.set(name, new Provider({
1107
+ name,
1108
+ component: ProviderFC
1109
+ }));
1110
+ }
1111
+ return this;
1112
+ }
1113
+ get derived() {
1114
+ if (!this.#derived) {
1115
+ const tokens = /* @__PURE__ */ new Map();
1116
+ const cssVarRefs = {};
1117
+ for (const [namespace, group] of this.tokenGroups) {
1118
+ const groupRefs = {};
1119
+ for (const [name, token] of group.tokens) {
1120
+ tokens.set(`${namespace}/${name}`, token);
1121
+ groupRefs[name] = token.derived.cssVarRef;
1122
+ }
1123
+ cssVarRefs[namespace] = groupRefs;
1124
+ }
1125
+ const modeOptionsByModifier = /* @__PURE__ */ new Map();
1126
+ for (const modifier of this.modifiers.values()) {
1127
+ const selector = modifier.selector;
1128
+ if (!isModeRef(selector)) continue;
1129
+ const option = this.#resolveModeOption(selector.ref);
1130
+ if (option) modeOptionsByModifier.set(modifier.name, option);
1131
+ }
1132
+ this.#derived = {
1133
+ tokens,
1134
+ cssVarRefs,
1135
+ modeOptionsByModifier
1136
+ };
1137
+ }
1138
+ return this.#derived;
1139
+ }
1140
+ #invalidate() {
1141
+ this.#derived = void 0;
1142
+ }
1143
+ #stylePropertiesFor(namespace) {
1144
+ const out = [];
1145
+ for (const styleProp of this.styleProps.values()) if (styleProp.consumedTokenGroups().includes(namespace)) out.push(styleProp.name);
1146
+ return out;
1147
+ }
1148
+ /**
1149
+ * Resolve a shorthand style-prop value to the qualified token it names —
1150
+ * `('bg', 'tertiary')` → `'bg/tertiary'` — by checking the style prop's
1151
+ * token groups for a token of that name. Returns `undefined` when the key
1152
+ * isn't a registered style prop, or the value is a keyword / arbitrary
1153
+ * literal rather than a token. Feeds `Component.derived.tokenRefs` so
1154
+ * shorthand token usage is captured structurally, not by string matching.
1155
+ */
1156
+ #resolveStylePropValueToken(styleProp, value) {
1157
+ const sp = this.styleProps.get(styleProp);
1158
+ if (!sp) return void 0;
1159
+ for (const namespace of sp.consumedTokenGroups()) if (this.tokenGroups.get(namespace)?.tokens.has(value)) return `${namespace}/${value}`;
1160
+ }
1161
+ *#tokensInGroup(namespace) {
1162
+ const group = this.tokenGroups.get(namespace);
1163
+ if (!group) return;
1164
+ for (const token of group.tokens.values()) yield token;
1165
+ }
1166
+ #lookupCompositeSelector(ref) {
1167
+ const [name, variant] = ref.split("/");
1168
+ if (!name || !variant) return void 0;
1169
+ const composite = this.compositeStyles.get(name);
1170
+ if (!composite) return void 0;
1171
+ if (!composite.styles.has(variant)) return void 0;
1172
+ const { markerVarName, markerVarValue } = composite.derived;
1173
+ return `@container style(${markerVarName}: ${markerVarValue(variant)})`;
1174
+ }
1175
+ /**
1176
+ * Resolve a `mode()` ref (`'colorMode/dark'`) to its `ModeOption`. The single
1177
+ * place that knows the ref's `group/option` shape — both the CSS-selector
1178
+ * lookup and the modifier→mode reverse map go through here, so the ref syntax
1179
+ * lives in exactly one spot.
1180
+ */
1181
+ #resolveModeOption(ref) {
1182
+ const [name, optionName] = ref.split("/");
1183
+ if (!name || !optionName) return void 0;
1184
+ return this.modes.get(name)?.options.get(optionName);
1185
+ }
1186
+ #lookupModeSelector(ref) {
1187
+ return this.#resolveModeOption(ref)?.css;
1188
+ }
1189
+ /**
1190
+ * Validate cross-references. Throws on cycles in token aliases or on
1191
+ * unknown refs in markers (`token()`, `composite()`, `mode()`,
1192
+ * `styleProp()`, `tokenGroup()`).
1193
+ */
1194
+ validate() {
1195
+ const tokens = this.derived.tokens;
1196
+ for (const [qualified, token] of tokens) visit(qualified, token, tokens, /* @__PURE__ */ new Set());
1197
+ for (const styleProp of this.styleProps.values()) for (const groupName of styleProp.consumedTokenGroups()) if (!this.tokenGroups.has(groupName)) throw new Error(`Style prop '${styleProp.name}' references unknown token group '${groupName}'`);
1198
+ for (const modifier of this.modifiers.values()) {
1199
+ const selector = modifier.selector;
1200
+ if (typeof selector === "object" && selector !== null) {
1201
+ if (selector.__kind === "composite") {
1202
+ if (this.#lookupCompositeSelector(selector.ref) === void 0) throw new Error(`Modifier '${modifier.name}' references unknown composite '${selector.ref}'`);
1203
+ }
1204
+ if (selector.__kind === "mode") {
1205
+ if (this.#lookupModeSelector(selector.ref) === void 0) throw new Error(`Modifier '${modifier.name}' references unknown mode '${selector.ref}'`);
1206
+ }
1207
+ }
1208
+ }
1209
+ }
1210
+ /**
1211
+ * Deep-fork for Studio drafts. Re-builds every entity from `toJSON` /
1212
+ * `fromJSON` rather than sharing instances so the clone is fully
1213
+ * isolated.
1214
+ *
1215
+ * `overlay` shallow-merges into the serialized form before re-hydration —
1216
+ * lets callers swap top-level slices (`{ components, tokenGroups }`)
1217
+ * without round-tripping through the chain methods. Each overlay key
1218
+ * fully replaces the existing slice (no per-entry merge); pass the
1219
+ * full record you want for that slice.
1220
+ */
1221
+ clone(overlay) {
1222
+ const base = this.toJSON();
1223
+ return Config.fromJSON(overlay ? {
1224
+ ...base,
1225
+ ...overlay
1226
+ } : base);
1227
+ }
1228
+ /**
1229
+ * Serialize to the wire format. Derived data is never included.
1230
+ * `meta.builtAt` lands in the JSON when the caller passes it (CLI
1231
+ * stamps it at write time so re-serializing in tests stays stable).
1232
+ * `meta.projectRoot` relativizes per-component `sourceFilePath`
1233
+ * values at the wire boundary — keeps `config.json` portable across
1234
+ * machines. Omitting it leaves paths absolute.
1235
+ */
1236
+ toJSON(meta) {
1237
+ const out = {
1238
+ schemaVersion: Config.SCHEMA_VERSION,
1239
+ prefix: this.prefix,
1240
+ preflight: this.preflight
1241
+ };
1242
+ if (meta?.builtAt !== void 0) out.builtAt = meta.builtAt;
1243
+ if (this.namespace !== void 0) out.namespace = this.namespace;
1244
+ if (this.designPrinciples.length > 0) out.designPrinciples = this.designPrinciples;
1245
+ if (Object.keys(this.globalStyles).length > 0) out.globalStyles = this.globalStyles;
1246
+ if (this.rawCss.length > 0) out.rawCss = this.rawCss;
1247
+ if (this.modes.size > 0) out.modes = mapToRecord(this.modes, (mode) => mode.toJSON());
1248
+ if (this.modifiers.size > 0) out.modifiers = mapToRecord(this.modifiers, stripName);
1249
+ if (this.tokenGroups.size > 0) out.tokenGroups = mapToRecord(this.tokenGroups, (group) => group.toJSON());
1250
+ if (this.styleProps.size > 0) out.styleProps = mapToRecord(this.styleProps, stripName);
1251
+ if (this.compositeStyles.size > 0) out.compositeStyles = mapToRecord(this.compositeStyles, stripName);
1252
+ if (this.motion.size > 0) {
1253
+ const motionRecord = {};
1254
+ for (const [name, motion] of this.motion) {
1255
+ const { name: _omit, ...rest } = motion.toJSON();
1256
+ motionRecord[name] = rest;
1257
+ }
1258
+ out.motion = motionRecord;
1259
+ }
1260
+ if (this.components.size > 0) out.components = mapToRecord(this.components, (component) => {
1261
+ const { name: _name, ...rest } = component.toJSON({ projectRoot: meta?.projectRoot });
1262
+ return rest;
1263
+ });
1264
+ if (this.componentGroups.size > 0) out.componentGroups = mapToRecord(this.componentGroups, (group) => {
1265
+ const { name: _omit, ...rest } = group.toJSON();
1266
+ return rest;
1267
+ });
1268
+ if (this.assetGroups.size > 0) out.assetGroups = mapToRecord(this.assetGroups, (group) => {
1269
+ const { name: _omit, ...rest } = group.toJSON();
1270
+ return rest;
1271
+ });
1272
+ if (this.providers.size > 0) {
1273
+ const providers = {};
1274
+ for (const name of this.providers.keys()) providers[name] = {};
1275
+ out.providers = providers;
1276
+ }
1277
+ return out;
1278
+ }
1279
+ /**
1280
+ * Hydrate from the wire format. Throws when `schemaVersion` doesn't
1281
+ * match — the CLI's job is to translate the error into "your manifest
1282
+ * is from an older build; run `uds build`."
1283
+ */
1284
+ static fromJSON(json) {
1285
+ if (json.schemaVersion !== Config.SCHEMA_VERSION) throw new Error(`Config schema version mismatch: file is v${json.schemaVersion}, runtime expects v${Config.SCHEMA_VERSION}. Re-run \`uds build\`.`);
1286
+ const config = new Config();
1287
+ config.prefix = json.prefix;
1288
+ config.preflight = json.preflight;
1289
+ if (json.namespace !== void 0) config.namespace = json.namespace;
1290
+ if (json.designPrinciples) config.designPrinciples = [...json.designPrinciples];
1291
+ if (json.globalStyles) config.globalStyles = json.globalStyles;
1292
+ if (json.rawCss) config.rawCss = [...json.rawCss];
1293
+ if (json.modes) config.registerModes(json.modes);
1294
+ if (json.tokenGroups) config.registerTokenGroups(json.tokenGroups);
1295
+ if (json.styleProps) config.registerStyleProps(json.styleProps);
1296
+ if (json.modifiers) config.registerModifiers(json.modifiers);
1297
+ if (json.compositeStyles) config.registerComposites(json.compositeStyles);
1298
+ if (json.motion) config.registerMotion(json.motion);
1299
+ const componentsByName = json.components ?? {};
1300
+ for (const def of Object.values(componentsByName)) if (def.sourceFilePath) attachSourcePath(def, def.sourceFilePath);
1301
+ const isSubcomponent = /* @__PURE__ */ new Set();
1302
+ for (const parentDef of Object.values(componentsByName)) {
1303
+ const subs = parentDef.subcomponents;
1304
+ if (!subs || subs.length === 0) continue;
1305
+ const childMap = {};
1306
+ for (const childName of subs) {
1307
+ const child = componentsByName[childName];
1308
+ if (!child) continue;
1309
+ childMap[childName] = child;
1310
+ isSubcomponent.add(childName);
1311
+ }
1312
+ if (Object.keys(childMap).length === 0) continue;
1313
+ Object.defineProperty(parentDef, "__subcomponents", {
1314
+ enumerable: false,
1315
+ configurable: true,
1316
+ writable: true,
1317
+ value: childMap
1318
+ });
1319
+ }
1320
+ const grouped = /* @__PURE__ */ new Set();
1321
+ if (json.componentGroups) {
1322
+ const groupDefs = {};
1323
+ for (const [groupKey, group] of Object.entries(json.componentGroups)) {
1324
+ const components = {};
1325
+ for (const name of group.components) {
1326
+ if (isSubcomponent.has(name)) continue;
1327
+ const def = componentsByName[name];
1328
+ if (def === void 0) throw new Error(`Config.fromJSON: componentGroup "${groupKey}" references unknown component "${name}"`);
1329
+ components[name] = def;
1330
+ grouped.add(name);
1331
+ }
1332
+ groupDefs[groupKey] = {
1333
+ label: group.label,
1334
+ ...group.description !== void 0 ? { description: group.description } : {},
1335
+ components
1336
+ };
1337
+ }
1338
+ config.registerComponentGroups(groupDefs);
1339
+ }
1340
+ if (json.components) {
1341
+ const ungrouped = {};
1342
+ for (const [name, def] of Object.entries(componentsByName)) {
1343
+ if (grouped.has(name) || isSubcomponent.has(name)) continue;
1344
+ ungrouped[name] = def;
1345
+ }
1346
+ if (Object.keys(ungrouped).length > 0) config.registerComponents(ungrouped);
1347
+ }
1348
+ if (json.assetGroups) for (const [slug, group] of Object.entries(json.assetGroups)) config.assetGroups.set(slug, AssetGroup.fromJSON(slug, group, () => config.namespace));
1349
+ return config;
1350
+ }
1351
+ };
1352
+ /**
1353
+ * Structural validation for a registry namespace. Guards only the
1354
+ * invariants the namespacing scheme depends on — non-empty, no `:`
1355
+ * (the `<namespace>:<Component>` separator), no whitespace. Stricter
1356
+ * format rules and uniqueness / ownership are enforced at push time
1357
+ * against the registered systems. Returns the value unchanged on
1358
+ * success; throws otherwise.
1359
+ */
1360
+ /**
1361
+ * Guard against registering two components under the same name. The name
1362
+ * is the component's identity (`<namespace>:<name>`, the spec `type`), so
1363
+ * a collision is unresolvable — a spec referencing it couldn't tell the
1364
+ * two apart. Thrown across every registration path (flat + grouped +
1365
+ * cross-call) so author-time mistakes fail loudly instead of silently
1366
+ * shadowing a component. Two components that should *display* the same
1367
+ * use distinct names + `metadata.label`.
1368
+ */
1369
+ /** A non-null object — a nested style bag (per-slot styles or a `_<modifier>`
1370
+ * sub-object) to descend into when walking component base styles. */
1371
+ function isStyleRecord(value) {
1372
+ return typeof value === "object" && value !== null;
1373
+ }
1374
+ function assertNameAvailable(components, name, namespace) {
1375
+ if (!components.has(name)) return;
1376
+ const qualified = namespace ? `${namespace}:${name}` : name;
1377
+ throw new Error(`Config: component "${name}" is already registered (identity "${qualified}") — component names must be unique within a registry. Rename one (e.g. "Studio${name}") and give it metadata.label "${name}" for a shared display name.`);
1378
+ }
1379
+ /**
1380
+ * Split a spec element `type` / registry key into its namespace + bare name.
1381
+ * `'uds:Text'` → `{ namespace: 'uds', name: 'Text' }`; a bare `'Text'` →
1382
+ * `{ name: 'Text' }`. The namespace is everything before the first `:`
1383
+ * (namespaces are validated to contain no `:`, see {@link validateNamespace}),
1384
+ * so a scoped namespace like `'@acme/system:Button'` still splits on the right
1385
+ * colon. The inverse of {@link Component.registryKey}.
1386
+ */
1387
+ function parseRegistryKey(type) {
1388
+ const sep = type.indexOf(":");
1389
+ if (sep < 0) return { name: type };
1390
+ return {
1391
+ namespace: type.slice(0, sep),
1392
+ name: type.slice(sep + 1)
1393
+ };
1394
+ }
1395
+ function validateNamespace(value) {
1396
+ if (value.length === 0) throw new Error("Config.configure: namespace must be non-empty.");
1397
+ if (value.includes(":")) throw new Error(`Config.configure: namespace "${value}" must not contain ":" — it's the reserved \`<namespace>:<Component>\` separator.`);
1398
+ if (/\s/.test(value)) throw new Error(`Config.configure: namespace "${value}" must not contain whitespace.`);
1399
+ if (value === "foreign") throw new Error(`Config.configure: namespace "${FOREIGN_NAMESPACE}" is reserved for third-party leaf components (the \`${FOREIGN_NAMESPACE}:<Component>\` sentinel).`);
1400
+ return value;
1401
+ }
1402
+ function visit(qualified, token, tokens, seen) {
1403
+ if (seen.has(qualified)) throw new Error(`Token cycle detected at '${qualified}'`);
1404
+ const value = token.value;
1405
+ if (typeof value === "object" && value !== null && "ref" in value && typeof value.ref === "string") {
1406
+ const next = tokens.get(value.ref);
1407
+ if (!next) throw new Error(`Token '${qualified}' references unknown token '${value.ref}'`);
1408
+ visit(value.ref, next, tokens, new Set(seen).add(qualified));
1409
+ }
1410
+ }
1411
+ function stripName(entity) {
1412
+ const { name: _name, namespace: _namespace, ...rest } = entity.toJSON();
1413
+ return rest;
1414
+ }
1415
+ /**
1416
+ * Default asset-group label from its slug — `'icons'` → `'Icons'`,
1417
+ * `'brand-icons'` → `'Brand Icons'`. `label` is optional and defaults
1418
+ * to the title-cased registration key.
1419
+ */
1420
+ function titleCaseSlug(slug) {
1421
+ return slug.split(/[-_]/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join(" ");
1422
+ }
1423
+ function mapToRecord(map, serializer) {
1424
+ const out = {};
1425
+ for (const [key, value] of map) out[key] = serializer(value);
1426
+ return out;
1427
+ }
1428
+ //#endregion
1429
+ export { Config, parseRegistryKey };