@yahoo/uds-create-config 2.20.0 → 2.21.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.
- package/dist/Component.d.ts +7 -1
- package/dist/Component.js +104 -3
- package/dist/Config.d.ts +34 -29
- package/dist/Config.js +102 -74
- package/dist/Token.d.ts +8 -1
- package/dist/Token.js +13 -11
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -2
- package/dist/native/NativeConfig.d.ts +1 -1
- package/dist/native/NativeConfig.js +1 -1
- package/dist/native/types.d.ts +2 -2
- package/dist/native/validation.js +3 -3
- package/dist/registry-aliases.d.ts +33 -0
- package/dist/registry-aliases.js +27 -0
- package/dist/registry-dir.d.ts +2 -2
- package/dist/registry-dir.js +1 -1
- package/dist/renderer/wrappers/void-elements.js +13 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +16 -22
- package/package.json +3 -3
package/dist/Component.d.ts
CHANGED
|
@@ -320,5 +320,11 @@ interface StylePropKeyword {
|
|
|
320
320
|
readonly styleProp: string;
|
|
321
321
|
readonly value: string;
|
|
322
322
|
}
|
|
323
|
+
/**
|
|
324
|
+
* HTML void elements — tags that must never receive children (React throws).
|
|
325
|
+
* Mirrors the HTML spec's void-element list; codegen's registry descriptor
|
|
326
|
+
* derives its `voidComponents` set from the same tags.
|
|
327
|
+
*/
|
|
328
|
+
declare const VOID_HTML_TAGS: ReadonlySet<string>;
|
|
323
329
|
//#endregion
|
|
324
|
-
export { Component, ComponentKind, CompositeInfo, FunctionInfo, Layer, PrimitivePropKind, PropBase, PropInfo, PropKind, ResolvedCompoundOverrides, SlotInfo, SlotValueKind, StylePropInfo, StylePropKeyword, StylePropTokenUsage, VariantInfo };
|
|
330
|
+
export { Component, ComponentKind, CompositeInfo, FunctionInfo, Layer, PrimitivePropKind, PropBase, PropInfo, PropKind, ResolvedCompoundOverrides, SlotInfo, SlotValueKind, StylePropInfo, StylePropKeyword, StylePropTokenUsage, VOID_HTML_TAGS, VariantInfo };
|
package/dist/Component.js
CHANGED
|
@@ -285,6 +285,7 @@ var Component = class {
|
|
|
285
285
|
propsIndex.set(propName, info);
|
|
286
286
|
}
|
|
287
287
|
const styleUsage = collectStyleUsage(this.base, this.props, this.compoundProps, this.#resolveStylePropValueToken, this.#isStyleProp);
|
|
288
|
+
const refusesChildren = this.#rootRefusesChildren();
|
|
288
289
|
this.#derived = {
|
|
289
290
|
layers: Array.from(this.layers.values()),
|
|
290
291
|
classNames: componentClassNames(this.#prefixGetter?.() ?? "", this.name),
|
|
@@ -300,8 +301,8 @@ var Component = class {
|
|
|
300
301
|
stylePropTokenUsages: styleUsage.stylePropTokenUsages,
|
|
301
302
|
stylePropKeywords: styleUsage.stylePropKeywords,
|
|
302
303
|
kind: this.#deriveKind(),
|
|
303
|
-
acceptsChildren: derivesAcceptsChildren(this.layers, propsArray),
|
|
304
|
-
acceptsChildNodes: derivesAcceptsChildNodes(this.layers, propsArray),
|
|
304
|
+
acceptsChildren: !refusesChildren && derivesAcceptsChildren(this.layers, propsArray),
|
|
305
|
+
acceptsChildNodes: !refusesChildren && derivesAcceptsChildNodes(this.layers, propsArray),
|
|
305
306
|
prop(propName) {
|
|
306
307
|
return propsIndex.get(propName);
|
|
307
308
|
}
|
|
@@ -309,6 +310,36 @@ var Component = class {
|
|
|
309
310
|
}
|
|
310
311
|
return this.#derived;
|
|
311
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* Whether this component's root element can never hold children, making
|
|
315
|
+
* `acceptsChildren`/`acceptsChildNodes` false regardless of the structural
|
|
316
|
+
* signals those derivations read. Two authored facts qualify (UDS-3068):
|
|
317
|
+
*
|
|
318
|
+
* - **A void root tag.** A single-primitive-root component whose effective
|
|
319
|
+
* tag (own `defaultProps.as`, else the root layer's tag, else the
|
|
320
|
+
* value-extends chain's) is a void HTML element (`input`, `img`, …) —
|
|
321
|
+
* React throws if such an element receives children, so treating it as a
|
|
322
|
+
* container injects drop-target Slots that crash every spec render of it.
|
|
323
|
+
* Gated on the single-primitive-root shape: a composed component's render
|
|
324
|
+
* decides where children go, so its root tag says nothing about them.
|
|
325
|
+
*
|
|
326
|
+
* - **An explicitly dropped `children` prop.** The authored
|
|
327
|
+
* `children: undefined` (the documented pattern for void/value elements —
|
|
328
|
+
* Input, Image, Textarea) is detected two ways: authored configs keep the
|
|
329
|
+
* key with an `undefined` value, which IS the drop; hydrated configs
|
|
330
|
+
* compact the key away, so there it's absence-against-ancestry (the
|
|
331
|
+
* merged props lack `children` while an ancestor declares it — only a
|
|
332
|
+
* drop can produce that, since value-extension inherits the parent's
|
|
333
|
+
* full prop surface). This catches `textarea`, which isn't HTML-void yet
|
|
334
|
+
* still rejects element children in React.
|
|
335
|
+
*/
|
|
336
|
+
#rootRefusesChildren() {
|
|
337
|
+
if (hasSinglePrimitiveRoot(this.layers)) {
|
|
338
|
+
const tag = resolveRootHtmlTag(this, this.#componentResolver, /* @__PURE__ */ new Set());
|
|
339
|
+
if (tag !== void 0 && VOID_HTML_TAGS.has(tag)) return true;
|
|
340
|
+
}
|
|
341
|
+
return droppedInheritedChildren(this, this.#componentResolver);
|
|
342
|
+
}
|
|
312
343
|
/** Classify authoring shape: `extends` wins (value-extension is the defining
|
|
313
344
|
* trait), then anything with multiple layers or a custom render is
|
|
314
345
|
* `multi-layer`, else a bare single-tag `primitive`. */
|
|
@@ -880,6 +911,76 @@ function hasSinglePrimitiveRoot(layers) {
|
|
|
880
911
|
return false;
|
|
881
912
|
}
|
|
882
913
|
/**
|
|
914
|
+
* HTML void elements — tags that must never receive children (React throws).
|
|
915
|
+
* Mirrors the HTML spec's void-element list; codegen's registry descriptor
|
|
916
|
+
* derives its `voidComponents` set from the same tags.
|
|
917
|
+
*/
|
|
918
|
+
const VOID_HTML_TAGS = new Set([
|
|
919
|
+
"area",
|
|
920
|
+
"base",
|
|
921
|
+
"br",
|
|
922
|
+
"col",
|
|
923
|
+
"embed",
|
|
924
|
+
"hr",
|
|
925
|
+
"img",
|
|
926
|
+
"input",
|
|
927
|
+
"link",
|
|
928
|
+
"meta",
|
|
929
|
+
"param",
|
|
930
|
+
"source",
|
|
931
|
+
"track",
|
|
932
|
+
"wbr"
|
|
933
|
+
]);
|
|
934
|
+
/**
|
|
935
|
+
* The HTML tag a component's root element renders as: own `defaultProps.as`
|
|
936
|
+
* wins (the `Box as="input"` value-extension pattern), else the root layer's
|
|
937
|
+
* tag, else the value-extends chain's answer. Undefined when nothing resolves
|
|
938
|
+
* (composed roots, unknown parents) — callers treat that as "no tag fact".
|
|
939
|
+
* The `seen` guard stops extends cycles.
|
|
940
|
+
*
|
|
941
|
+
* Mirrors codegen's `resolveHtmlTag` (getReactArtifacts.ts), which reads the
|
|
942
|
+
* same facts off `derived.layers[*].renders` — keep the precedence order
|
|
943
|
+
* (`as` → root tag → extends chain) in sync.
|
|
944
|
+
*/
|
|
945
|
+
function resolveRootHtmlTag(component, resolve, seen) {
|
|
946
|
+
if (seen.has(component.name)) return void 0;
|
|
947
|
+
seen.add(component.name);
|
|
948
|
+
const as = component.defaultProps?.as;
|
|
949
|
+
if (typeof as === "string") return as;
|
|
950
|
+
const t = component.layers.get("root")?.tag;
|
|
951
|
+
if (typeof t === "string") return t;
|
|
952
|
+
if (typeof t === "object" && t !== null) {
|
|
953
|
+
const marker = t;
|
|
954
|
+
if (marker.__kind === "tag" && typeof marker.ref === "string") return marker.ref;
|
|
955
|
+
}
|
|
956
|
+
const parent = component.extendsFrom && resolve ? resolve(component.extendsFrom) : void 0;
|
|
957
|
+
return parent ? resolveRootHtmlTag(parent, resolve, seen) : void 0;
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Whether the author dropped an inherited `children` prop (`children:
|
|
961
|
+
* undefined` in a value-extension — Input/Image/Textarea dropping Box's
|
|
962
|
+
* slot). Two-tier detection: authored configs keep the dropped key with an
|
|
963
|
+
* `undefined` value (the direct trace); hydrated configs compact the key
|
|
964
|
+
* away, leaving absence-against-ancestry — this component's merged props
|
|
965
|
+
* lack `children` while some ancestor declares it. A component that never
|
|
966
|
+
* had a `children` prop anywhere in its chain isn't a drop — it just
|
|
967
|
+
* doesn't participate.
|
|
968
|
+
*/
|
|
969
|
+
function droppedInheritedChildren(component, resolve) {
|
|
970
|
+
if (Object.hasOwn(component.props, "children")) return component.props.children === void 0;
|
|
971
|
+
if (!resolve) return false;
|
|
972
|
+
const seen = new Set([component.name]);
|
|
973
|
+
let parentName = component.extendsFrom;
|
|
974
|
+
while (parentName && !seen.has(parentName)) {
|
|
975
|
+
seen.add(parentName);
|
|
976
|
+
const parent = resolve(parentName);
|
|
977
|
+
if (!parent) return false;
|
|
978
|
+
if (parent.props.children !== void 0) return true;
|
|
979
|
+
parentName = parent.extendsFrom;
|
|
980
|
+
}
|
|
981
|
+
return false;
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
883
984
|
* `acceptsChildren` is true when either:
|
|
884
985
|
* - the component has a single primitive root layer, OR
|
|
885
986
|
* - the component declares a `children` slot prop (`children: slot(...)`).
|
|
@@ -938,4 +1039,4 @@ function buildCompoundOverrides(entries) {
|
|
|
938
1039
|
return out;
|
|
939
1040
|
}
|
|
940
1041
|
//#endregion
|
|
941
|
-
export { Component, Layer, assertDefinitionAcceptsPlaceable };
|
|
1042
|
+
export { Component, Layer, VOID_HTML_TAGS, assertDefinitionAcceptsPlaceable };
|
package/dist/Config.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CssVarRef } from "./types/css-values.js";
|
|
2
|
-
import { AnyStylePropDefinition, BuildOptions, CanvasRole, CanvasRoleEntry, CanvasRolesDefinitionInput, CanvasRolesInputConstraint, ComponentDefinition, ComponentGroupDefinition, CompositeStyleDefinition, GlobalStylesDef,
|
|
2
|
+
import { AnyStylePropDefinition, BuildOptions, CanvasRole, CanvasRoleEntry, CanvasRolesDefinitionInput, CanvasRolesInputConstraint, ComponentDefinition, ComponentGroupDefinition, CompositeStyleDefinition, GlobalStylesDef, LinkedSystemPin, LinkedSystemsInput, ModeDefinition, ModifierDefinition, MotionDefinitionInput, PlaygroundOptions, SerializedConfig, SystemLink, TokenGroupDefinition, TokenModifierKey } from "./types.js";
|
|
3
3
|
import { AssetGroupDefinition } from "./defineAssetGroup.js";
|
|
4
4
|
import { AssetGroup } from "./AssetGroup.js";
|
|
5
5
|
import { cssVar } from "./brands.js";
|
|
@@ -94,11 +94,12 @@ declare class Config {
|
|
|
94
94
|
readonly motion: Map<string, MotionDef>;
|
|
95
95
|
readonly canvasRoles: Map<CanvasRole, readonly CanvasRoleEntry[]>;
|
|
96
96
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
97
|
+
* Source systems pinned via {@link registerLinkedSystems}, keyed by the
|
|
98
|
+
* source system's database UUID. A pin anchors every linked value from that
|
|
99
|
+
* system to one exact published version — registration validates each
|
|
100
|
+
* link-marked value against it.
|
|
100
101
|
*/
|
|
101
|
-
readonly
|
|
102
|
+
readonly linkedSystems: Map<string, LinkedSystemPin>;
|
|
102
103
|
/**
|
|
103
104
|
* Set top-level config metadata in one call — the single scalar-setter,
|
|
104
105
|
* replacing the former `withPrefix` / `withPreflight` / `withName` /
|
|
@@ -149,20 +150,6 @@ declare class Config {
|
|
|
149
150
|
* `config.components.get(type)` when `type` may be a spec/registry key.
|
|
150
151
|
*/
|
|
151
152
|
getComponent(type: string): Component | undefined;
|
|
152
|
-
/**
|
|
153
|
-
* Identify an id-qualified linked-component reference
|
|
154
|
-
* (`'<systemId>:<Name>'`, e.g. `'96d1678f-…:Button'`). Returns the parsed
|
|
155
|
-
* `{ systemId, name }` when the system id is a registered
|
|
156
|
-
* {@link linkedComponents} entry AND the bare name is declared in its
|
|
157
|
-
* `components` list; `undefined` otherwise (including bare names and
|
|
158
|
-
* namespaced registry keys). This is declaration-lookup only — it never
|
|
159
|
-
* returns a renderable {@link Component}; resolution is deferred to the
|
|
160
|
-
* materialization/rendering work.
|
|
161
|
-
*/
|
|
162
|
-
getLinkedComponentRef(type: string): {
|
|
163
|
-
systemId: string;
|
|
164
|
-
name: string;
|
|
165
|
-
} | undefined;
|
|
166
153
|
/**
|
|
167
154
|
* The bare, human-readable component name for a spec `type` — strips this
|
|
168
155
|
* registry's `<namespace>:` prefix (`'uds:Text'` → `'Text'`) for display, and
|
|
@@ -331,18 +318,19 @@ declare class Config {
|
|
|
331
318
|
registerComposites(composites: Record<string, CompositeStyleDefinition>): this;
|
|
332
319
|
registerMotion(motion: Record<string, MotionDefinitionInput>): this;
|
|
333
320
|
/**
|
|
334
|
-
*
|
|
335
|
-
* source system's database UUID (the identity — globally unique,
|
|
336
|
-
* rename-proof); the
|
|
337
|
-
* link time
|
|
338
|
-
* linked components.
|
|
321
|
+
* Pin the source systems this config links values from. Each record key is
|
|
322
|
+
* the source system's database UUID (the identity — globally unique,
|
|
323
|
+
* immutable, rename-proof); the pin carries the system's display-name
|
|
324
|
+
* snapshot at link time and an exact published version.
|
|
339
325
|
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
326
|
+
* One pin per system, one version per pin. Every linked value registered
|
|
327
|
+
* afterwards (a barrel value carrying a `link` marker) is validated against
|
|
328
|
+
* its pin — an unpinned system or a version mismatch throws at
|
|
329
|
+
* registration, so pins must appear before the `register*` calls that use
|
|
330
|
+
* them. The pin's `name` also feeds the registry folder slug the pull
|
|
331
|
+
* pipeline derives.
|
|
344
332
|
*/
|
|
345
|
-
|
|
333
|
+
registerLinkedSystems(input: LinkedSystemsInput): this;
|
|
346
334
|
registerComponents(components: Record<string, ComponentDefinition>): this;
|
|
347
335
|
/**
|
|
348
336
|
* Build a single component from its definition with this config's context
|
|
@@ -437,6 +425,13 @@ declare class Config {
|
|
|
437
425
|
registerProviders(providers: Record<string, ProviderComponent<Record<never, never>>>): this;
|
|
438
426
|
get derived(): {
|
|
439
427
|
readonly tokens: ReadonlyMap<string, Token>;
|
|
428
|
+
/**
|
|
429
|
+
* Provenance index for linked tokens — `"<systemId>:<sourceKey>"` →
|
|
430
|
+
* token. A `token()` ref authored inside a linked token names SOURCE-
|
|
431
|
+
* system groups, so it resolves through this index (rename-proof: the
|
|
432
|
+
* consumer's local group name never appears in the key).
|
|
433
|
+
*/
|
|
434
|
+
readonly tokensBySource: ReadonlyMap<string, Token>;
|
|
440
435
|
readonly cssVarRefs: Readonly<Record<string, Readonly<Record<string, CssVarRef>>>>;
|
|
441
436
|
/**
|
|
442
437
|
* Reverse lookup: a style modifier key (`_dark`) → the mode option that
|
|
@@ -446,6 +441,16 @@ declare class Config {
|
|
|
446
441
|
*/
|
|
447
442
|
readonly modeOptionsByModifier: ReadonlyMap<TokenModifierKey, ModeOption>;
|
|
448
443
|
};
|
|
444
|
+
/**
|
|
445
|
+
* Resolve a `token()` ref, provenance-aware — the ONE lookup every ref
|
|
446
|
+
* consumer (registration wiring, `validate()`, codegen emission) should
|
|
447
|
+
* use. A ref authored on a linked token (`context` set) names the SOURCE
|
|
448
|
+
* system's groups, so it resolves through the provenance index only —
|
|
449
|
+
* falling back to the consumer's own keys would let a coincidentally-named
|
|
450
|
+
* local group silently shadow a missing source group. Local refs resolve
|
|
451
|
+
* against consumer keys.
|
|
452
|
+
*/
|
|
453
|
+
resolveTokenRef(ref: string, context?: SystemLink): Token | undefined;
|
|
449
454
|
/**
|
|
450
455
|
* Validate cross-references. Throws on cycles in token aliases or on
|
|
451
456
|
* unknown refs in markers (`token()`, `composite()`, `mode()`,
|
package/dist/Config.js
CHANGED
|
@@ -65,11 +65,12 @@ var Config = class Config {
|
|
|
65
65
|
motion = /* @__PURE__ */ new Map();
|
|
66
66
|
canvasRoles = /* @__PURE__ */ new Map();
|
|
67
67
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
68
|
+
* Source systems pinned via {@link registerLinkedSystems}, keyed by the
|
|
69
|
+
* source system's database UUID. A pin anchors every linked value from that
|
|
70
|
+
* system to one exact published version — registration validates each
|
|
71
|
+
* link-marked value against it.
|
|
71
72
|
*/
|
|
72
|
-
|
|
73
|
+
linkedSystems = /* @__PURE__ */ new Map();
|
|
73
74
|
#derived;
|
|
74
75
|
/**
|
|
75
76
|
* Set top-level config metadata in one call — the single scalar-setter,
|
|
@@ -134,27 +135,6 @@ var Config = class Config {
|
|
|
134
135
|
if (namespace !== void 0 && namespace === this.namespace) return this.components.get(name);
|
|
135
136
|
}
|
|
136
137
|
/**
|
|
137
|
-
* Identify an id-qualified linked-component reference
|
|
138
|
-
* (`'<systemId>:<Name>'`, e.g. `'96d1678f-…:Button'`). Returns the parsed
|
|
139
|
-
* `{ systemId, name }` when the system id is a registered
|
|
140
|
-
* {@link linkedComponents} entry AND the bare name is declared in its
|
|
141
|
-
* `components` list; `undefined` otherwise (including bare names and
|
|
142
|
-
* namespaced registry keys). This is declaration-lookup only — it never
|
|
143
|
-
* returns a renderable {@link Component}; resolution is deferred to the
|
|
144
|
-
* materialization/rendering work.
|
|
145
|
-
*/
|
|
146
|
-
getLinkedComponentRef(type) {
|
|
147
|
-
const { namespace: systemId, name } = parseRegistryKey(type);
|
|
148
|
-
if (systemId === void 0) return void 0;
|
|
149
|
-
const entry = this.linkedComponents.get(systemId);
|
|
150
|
-
if (!entry) return void 0;
|
|
151
|
-
if (entry.components !== "all" && !entry.components.includes(name)) return;
|
|
152
|
-
return {
|
|
153
|
-
systemId,
|
|
154
|
-
name
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
138
|
* The bare, human-readable component name for a spec `type` — strips this
|
|
159
139
|
* registry's `<namespace>:` prefix (`'uds:Text'` → `'Text'`) for display, and
|
|
160
140
|
* the `foreign:` sentinel third-party leaves carry (`'foreign:CopyIcon'` →
|
|
@@ -900,13 +880,16 @@ var Config = class Config {
|
|
|
900
880
|
return this;
|
|
901
881
|
}
|
|
902
882
|
registerTokenGroups(groups) {
|
|
903
|
-
for (const [namespace, def] of Object.entries(groups))
|
|
904
|
-
namespace,
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
883
|
+
for (const [namespace, def] of Object.entries(groups)) {
|
|
884
|
+
for (const [tokenName, tokenDef] of Object.entries(def.tokens)) this.#assertLinkedTokenPinned(namespace, tokenName, tokenDef.link);
|
|
885
|
+
this.tokenGroups.set(namespace, new TokenGroup({
|
|
886
|
+
namespace,
|
|
887
|
+
prefixGetter: () => this.prefix,
|
|
888
|
+
definition: def,
|
|
889
|
+
tokenLookup: (ref, context) => this.resolveTokenRef(ref, context),
|
|
890
|
+
stylePropertiesLookup: (ns) => this.#stylePropertiesFor(ns)
|
|
891
|
+
}));
|
|
892
|
+
}
|
|
910
893
|
this.#invalidate();
|
|
911
894
|
return this;
|
|
912
895
|
}
|
|
@@ -938,24 +921,24 @@ var Config = class Config {
|
|
|
938
921
|
return this;
|
|
939
922
|
}
|
|
940
923
|
/**
|
|
941
|
-
*
|
|
942
|
-
* source system's database UUID (the identity — globally unique,
|
|
943
|
-
* rename-proof); the
|
|
944
|
-
* link time
|
|
945
|
-
* linked components.
|
|
924
|
+
* Pin the source systems this config links values from. Each record key is
|
|
925
|
+
* the source system's database UUID (the identity — globally unique,
|
|
926
|
+
* immutable, rename-proof); the pin carries the system's display-name
|
|
927
|
+
* snapshot at link time and an exact published version.
|
|
946
928
|
*
|
|
947
|
-
*
|
|
948
|
-
*
|
|
949
|
-
*
|
|
950
|
-
*
|
|
929
|
+
* One pin per system, one version per pin. Every linked value registered
|
|
930
|
+
* afterwards (a barrel value carrying a `link` marker) is validated against
|
|
931
|
+
* its pin — an unpinned system or a version mismatch throws at
|
|
932
|
+
* registration, so pins must appear before the `register*` calls that use
|
|
933
|
+
* them. The pin's `name` also feeds the registry folder slug the pull
|
|
934
|
+
* pipeline derives.
|
|
951
935
|
*/
|
|
952
|
-
|
|
953
|
-
for (const [systemId,
|
|
954
|
-
|
|
955
|
-
this.
|
|
956
|
-
name:
|
|
957
|
-
version:
|
|
958
|
-
components: entry.components === "all" ? "all" : [...entry.components]
|
|
936
|
+
registerLinkedSystems(input) {
|
|
937
|
+
for (const [systemId, pin] of Object.entries(input)) {
|
|
938
|
+
validateLinkedSystemPin(systemId, pin, this.linkedSystems);
|
|
939
|
+
this.linkedSystems.set(systemId, {
|
|
940
|
+
name: pin.name,
|
|
941
|
+
version: pin.version
|
|
959
942
|
});
|
|
960
943
|
}
|
|
961
944
|
return this;
|
|
@@ -1254,11 +1237,13 @@ var Config = class Config {
|
|
|
1254
1237
|
get derived() {
|
|
1255
1238
|
if (!this.#derived) {
|
|
1256
1239
|
const tokens = /* @__PURE__ */ new Map();
|
|
1240
|
+
const tokensBySource = /* @__PURE__ */ new Map();
|
|
1257
1241
|
const cssVarRefs = {};
|
|
1258
1242
|
for (const [namespace, group] of this.tokenGroups) {
|
|
1259
1243
|
const groupRefs = {};
|
|
1260
1244
|
for (const [name, token] of group.tokens) {
|
|
1261
1245
|
tokens.set(`${namespace}/${name}`, token);
|
|
1246
|
+
if (token.link) tokensBySource.set(`${token.link.systemId}:${token.link.sourceKey}`, token);
|
|
1262
1247
|
groupRefs[name] = token.derived.cssVarRef;
|
|
1263
1248
|
}
|
|
1264
1249
|
cssVarRefs[namespace] = groupRefs;
|
|
@@ -1272,12 +1257,39 @@ var Config = class Config {
|
|
|
1272
1257
|
}
|
|
1273
1258
|
this.#derived = {
|
|
1274
1259
|
tokens,
|
|
1260
|
+
tokensBySource,
|
|
1275
1261
|
cssVarRefs,
|
|
1276
1262
|
modeOptionsByModifier
|
|
1277
1263
|
};
|
|
1278
1264
|
}
|
|
1279
1265
|
return this.#derived;
|
|
1280
1266
|
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Resolve a `token()` ref, provenance-aware — the ONE lookup every ref
|
|
1269
|
+
* consumer (registration wiring, `validate()`, codegen emission) should
|
|
1270
|
+
* use. A ref authored on a linked token (`context` set) names the SOURCE
|
|
1271
|
+
* system's groups, so it resolves through the provenance index only —
|
|
1272
|
+
* falling back to the consumer's own keys would let a coincidentally-named
|
|
1273
|
+
* local group silently shadow a missing source group. Local refs resolve
|
|
1274
|
+
* against consumer keys.
|
|
1275
|
+
*/
|
|
1276
|
+
resolveTokenRef(ref, context) {
|
|
1277
|
+
if (context) return this.derived.tokensBySource.get(`${context.systemId}:${ref}`);
|
|
1278
|
+
return this.derived.tokens.get(ref);
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Pin-consistency gate for a link-marked token entering the registry.
|
|
1282
|
+
* Catches hand-forged markers (unpinned system), stale registry barrels
|
|
1283
|
+
* (version mismatch), and mis-kinded markers before they poison lookups.
|
|
1284
|
+
*/
|
|
1285
|
+
#assertLinkedTokenPinned(groupName, tokenName, link) {
|
|
1286
|
+
if (!link) return;
|
|
1287
|
+
const label = `Config.registerTokenGroups: token "${groupName}/${tokenName}"`;
|
|
1288
|
+
if (link.kind !== "token") throw new Error(`${label} carries a "${link.kind}" link marker — a token entry can only carry kind "token" (a token-group link spreads to per-token markers).`);
|
|
1289
|
+
const pin = this.linkedSystems.get(link.systemId);
|
|
1290
|
+
if (!pin) throw new Error(`${label} is linked from system "${link.systemId}", which has no registerLinkedSystems pin — pin the system (name + exact version) before registering values linked from it.`);
|
|
1291
|
+
if (pin.version !== link.systemVersion) throw new Error(`${label} is linked at version "${link.systemVersion}" but system "${pin.name}" is pinned at "${pin.version}" — the registry barrel is stale; re-run \`uds pull\`.`);
|
|
1292
|
+
}
|
|
1281
1293
|
#invalidate() {
|
|
1282
1294
|
this.#derived = void 0;
|
|
1283
1295
|
}
|
|
@@ -1334,7 +1346,7 @@ var Config = class Config {
|
|
|
1334
1346
|
*/
|
|
1335
1347
|
validate() {
|
|
1336
1348
|
const tokens = this.derived.tokens;
|
|
1337
|
-
for (const [qualified, token] of tokens) visit(qualified, token,
|
|
1349
|
+
for (const [qualified, token] of tokens) visit(qualified, token, (ref, context) => this.resolveTokenRef(ref, context), /* @__PURE__ */ new Set());
|
|
1338
1350
|
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}'`);
|
|
1339
1351
|
for (const modifier of this.modifiers.values()) {
|
|
1340
1352
|
const selector = modifier.selector;
|
|
@@ -1422,10 +1434,7 @@ var Config = class Config {
|
|
|
1422
1434
|
out.canvasRoles = canvasRoles;
|
|
1423
1435
|
}
|
|
1424
1436
|
if (this.playgroundOptions.port !== void 0) out.playgroundOptions = { port: this.playgroundOptions.port };
|
|
1425
|
-
if (this.
|
|
1426
|
-
...entry,
|
|
1427
|
-
components: entry.components === "all" ? "all" : [...entry.components]
|
|
1428
|
-
}));
|
|
1437
|
+
if (this.linkedSystems.size > 0) out.linkedSystems = mapToRecord(this.linkedSystems, (pin) => ({ ...pin }));
|
|
1429
1438
|
return out;
|
|
1430
1439
|
}
|
|
1431
1440
|
/**
|
|
@@ -1444,12 +1453,12 @@ var Config = class Config {
|
|
|
1444
1453
|
if (json.globalStyles) config.globalStyles = json.globalStyles;
|
|
1445
1454
|
if (json.rawCss) config.rawCss = [...json.rawCss];
|
|
1446
1455
|
if (json.modes) config.registerModes(json.modes);
|
|
1456
|
+
if (json.linkedSystems) config.registerLinkedSystems(json.linkedSystems);
|
|
1447
1457
|
if (json.tokenGroups) config.registerTokenGroups(json.tokenGroups);
|
|
1448
1458
|
if (json.styleProps) config.registerStyleProps(json.styleProps);
|
|
1449
1459
|
if (json.modifiers) config.registerModifiers(json.modifiers);
|
|
1450
1460
|
if (json.compositeStyles) config.registerComposites(json.compositeStyles);
|
|
1451
1461
|
if (json.motion) config.registerMotion(json.motion);
|
|
1452
|
-
if (json.linkedComponents) config.registerLinkedComponents(json.linkedComponents);
|
|
1453
1462
|
const componentsByName = json.components ?? {};
|
|
1454
1463
|
for (const def of Object.values(componentsByName)) if (def.sourceFilePath) attachSourcePath(def, def.sourceFilePath);
|
|
1455
1464
|
const isSubcomponent = /* @__PURE__ */ new Set();
|
|
@@ -1569,20 +1578,15 @@ function parseRegistryKey(type) {
|
|
|
1569
1578
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1570
1579
|
/** Exact `major.minor.patch` with optional prerelease/build — no ranges. */
|
|
1571
1580
|
const EXACT_SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
1572
|
-
function
|
|
1573
|
-
const label = `Config.
|
|
1574
|
-
if (!UUID_RE.test(systemId)) throw new Error(`${label} must be a UUID —
|
|
1575
|
-
if (existing.has(systemId))
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
if (
|
|
1580
|
-
|
|
1581
|
-
for (const name of entry.components) {
|
|
1582
|
-
if (name.trim().length === 0) throw new Error(`${label}: component names must be non-empty strings.`);
|
|
1583
|
-
if (seen.has(name)) throw new Error(`${label}: duplicate component "${name}".`);
|
|
1584
|
-
seen.add(name);
|
|
1585
|
-
}
|
|
1581
|
+
function validateLinkedSystemPin(systemId, pin, existing) {
|
|
1582
|
+
const label = `Config.registerLinkedSystems: system id "${systemId}"`;
|
|
1583
|
+
if (!UUID_RE.test(systemId)) throw new Error(`${label} must be a UUID — pins are keyed by the source system's database id, not its slug or name.`);
|
|
1584
|
+
if (existing.has(systemId)) {
|
|
1585
|
+
const prior = existing.get(systemId);
|
|
1586
|
+
throw new Error(`${label} is already pinned ("${prior?.name}" @ ${prior?.version}) — a config pins one version per system; update the existing pin instead of re-pinning.`);
|
|
1587
|
+
}
|
|
1588
|
+
if (pin.name.trim().length === 0) throw new Error(`${label}: name must be non-empty — it's the system's display-name snapshot at link time and feeds the registry folder slug.`);
|
|
1589
|
+
if (!EXACT_SEMVER_RE.test(pin.version)) throw new Error(`${label}: version "${pin.version}" must be an exact semver literal (e.g. "1.3.1") — ranges and tags are not supported.`);
|
|
1586
1590
|
}
|
|
1587
1591
|
function validateNamespace(value) {
|
|
1588
1592
|
if (value.length === 0) throw new Error("Config.configure: namespace must be non-empty.");
|
|
@@ -1591,15 +1595,39 @@ function validateNamespace(value) {
|
|
|
1591
1595
|
if (value === "foreign") throw new Error(`Config.configure: namespace "${FOREIGN_NAMESPACE}" is reserved for third-party leaf components (the \`${FOREIGN_NAMESPACE}:<Component>\` sentinel).`);
|
|
1592
1596
|
return value;
|
|
1593
1597
|
}
|
|
1594
|
-
|
|
1598
|
+
/**
|
|
1599
|
+
* Cross-reference walk for one token's alias chain, provenance-aware.
|
|
1600
|
+
* `resolve` is the config's `resolveTokenRef` — a ref on a linked token
|
|
1601
|
+
* resolves through the provenance index (source-system keys), a local ref
|
|
1602
|
+
* through consumer keys. A dangling ref on a linked token is a closure
|
|
1603
|
+
* violation (the source group it names was never linked), which gets its own
|
|
1604
|
+
* actionable error; a dangling local ref stays the classic unknown-token
|
|
1605
|
+
* error. Modifier refs are checked for linked tokens only — the closure rule
|
|
1606
|
+
* is new with linking, and local modifier behavior is unchanged.
|
|
1607
|
+
*/
|
|
1608
|
+
function visit(qualified, token, resolve, seen) {
|
|
1595
1609
|
if (seen.has(qualified)) throw new Error(`Token cycle detected at '${qualified}'`);
|
|
1596
|
-
const
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1610
|
+
const followRef = (ref) => {
|
|
1611
|
+
const next = resolve(ref, token.link);
|
|
1612
|
+
if (!next) {
|
|
1613
|
+
if (token.link) {
|
|
1614
|
+
const [sourceGroup] = ref.split("/");
|
|
1615
|
+
throw new Error(`Token '${qualified}' is linked from system "${token.link.systemId}" and references its token '${ref}', which nothing in this config provides — link the source system's "${sourceGroup}" group too (refs on linked tokens resolve by provenance, not by local group names).`);
|
|
1616
|
+
}
|
|
1617
|
+
throw new Error(`Token '${qualified}' references unknown token '${ref}'`);
|
|
1618
|
+
}
|
|
1619
|
+
visit(`${next.namespace}/${next.name}`, next, resolve, new Set(seen).add(qualified));
|
|
1620
|
+
};
|
|
1621
|
+
if (isTokenRefValue(token.value)) followRef(token.value.ref);
|
|
1622
|
+
if (token.link && token.modifiers) for (const value of Object.values(token.modifiers)) {
|
|
1623
|
+
const inner = typeof value === "object" && value !== null && "value" in value ? value.value : value;
|
|
1624
|
+
if (isTokenRefValue(inner)) followRef(inner.ref);
|
|
1601
1625
|
}
|
|
1602
1626
|
}
|
|
1627
|
+
/** A `token()` ref marker — `{ ref: 'ns/name' }` — in a value position. */
|
|
1628
|
+
function isTokenRefValue(value) {
|
|
1629
|
+
return typeof value === "object" && value !== null && "ref" in value && typeof value.ref === "string";
|
|
1630
|
+
}
|
|
1603
1631
|
function stripName(entity) {
|
|
1604
1632
|
const { name: _name, namespace: _namespace, ...rest } = entity.toJSON();
|
|
1605
1633
|
return rest;
|
package/dist/Token.d.ts
CHANGED
|
@@ -12,8 +12,15 @@ type ResolvedTokenLiteral = string | number | boolean | undefined;
|
|
|
12
12
|
* Looks up another token by `namespace/name`. Injected by the parent
|
|
13
13
|
* Config so `Token.derived.value` can chase `token()` aliases without
|
|
14
14
|
* holding a back-ref to the whole config object.
|
|
15
|
+
*
|
|
16
|
+
* `context` is the provenance of the token the ref was authored on. A ref
|
|
17
|
+
* inside a linked token is written against the SOURCE system's group names
|
|
18
|
+
* (`token('base/carbon-5')` in a group linked from Yahoo OS means Yahoo OS's
|
|
19
|
+
* `base` group, whatever the consumer renamed it to), so the lookup resolves
|
|
20
|
+
* it through the link markers instead of the consumer's own keys. Local
|
|
21
|
+
* tokens pass no context and resolve against the consumer's keys directly.
|
|
15
22
|
*/
|
|
16
|
-
type TokenSiblingLookup = (qualifiedName: string) => Token | undefined;
|
|
23
|
+
type TokenSiblingLookup = (qualifiedName: string, context?: SystemLink) => Token | undefined;
|
|
17
24
|
declare class Token {
|
|
18
25
|
#private;
|
|
19
26
|
readonly name: string;
|
package/dist/Token.js
CHANGED
|
@@ -43,6 +43,7 @@ var Token = class {
|
|
|
43
43
|
const tokenValue = this.value;
|
|
44
44
|
const tokenType = this.type;
|
|
45
45
|
const modifiers = this.modifiers;
|
|
46
|
+
const link = this.link;
|
|
46
47
|
const prefixGetter = this.#prefixGetter;
|
|
47
48
|
const lookup = this.#lookup;
|
|
48
49
|
const groupTypeGetter = this.#groupTypeGetter;
|
|
@@ -57,10 +58,10 @@ var Token = class {
|
|
|
57
58
|
return `var(${buildCssVar(prefixGetter(), namespace, name)})`;
|
|
58
59
|
},
|
|
59
60
|
get value() {
|
|
60
|
-
return chaseLiteral(tokenValue, lookup);
|
|
61
|
+
return chaseLiteral(tokenValue, lookup, link);
|
|
61
62
|
},
|
|
62
63
|
get cssValueType() {
|
|
63
|
-
const literal = chaseLiteral(tokenValue, lookup);
|
|
64
|
+
const literal = chaseLiteral(tokenValue, lookup, link);
|
|
64
65
|
return resolveTokenValueType({
|
|
65
66
|
value: typeof literal === "string" ? literal : String(tokenValue),
|
|
66
67
|
type: tokenType
|
|
@@ -68,7 +69,7 @@ var Token = class {
|
|
|
68
69
|
},
|
|
69
70
|
*modifierValues() {
|
|
70
71
|
if (!modifiers) return;
|
|
71
|
-
for (const [key, value] of Object.entries(modifiers)) yield [key, resolveModifierLiteral(value, lookup)];
|
|
72
|
+
for (const [key, value] of Object.entries(modifiers)) yield [key, resolveModifierLiteral(value, lookup, link)];
|
|
72
73
|
}
|
|
73
74
|
};
|
|
74
75
|
}
|
|
@@ -99,19 +100,20 @@ function collectModifiers(definition) {
|
|
|
99
100
|
}
|
|
100
101
|
return count > 0 ? modifiers : void 0;
|
|
101
102
|
}
|
|
102
|
-
function chaseLiteral(value, lookup, seen = /* @__PURE__ */ new Set()) {
|
|
103
|
+
function chaseLiteral(value, lookup, context, seen = /* @__PURE__ */ new Set()) {
|
|
103
104
|
if (isTokenRef(value)) {
|
|
104
|
-
|
|
105
|
-
seen.
|
|
106
|
-
|
|
105
|
+
const seenKey = `${context?.systemId ?? ""}:${value.ref}`;
|
|
106
|
+
if (seen.has(seenKey)) return void 0;
|
|
107
|
+
seen.add(seenKey);
|
|
108
|
+
const next = lookup(value.ref, context);
|
|
107
109
|
if (!next) return void 0;
|
|
108
|
-
return chaseLiteral(next.value, lookup, seen);
|
|
110
|
+
return chaseLiteral(next.value, lookup, next.link, seen);
|
|
109
111
|
}
|
|
110
112
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
111
113
|
}
|
|
112
|
-
function resolveModifierLiteral(value, lookup) {
|
|
113
|
-
if (typeof value === "object" && value !== null && "value" in value) return chaseLiteral(value.value, lookup);
|
|
114
|
-
return chaseLiteral(value, lookup);
|
|
114
|
+
function resolveModifierLiteral(value, lookup, context) {
|
|
115
|
+
if (typeof value === "object" && value !== null && "value" in value) return chaseLiteral(value.value, lookup, context);
|
|
116
|
+
return chaseLiteral(value, lookup, context);
|
|
115
117
|
}
|
|
116
118
|
//#endregion
|
|
117
119
|
export { Token };
|