@yahoo/uds-create-config 4.6.0 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/configs/CanvasConfig.d.ts +900 -80
  2. package/dist/configs/react-native-system.d.ts +603 -55
  3. package/dist/configs/system.d.ts +603 -55
  4. package/dist/configs/system.js +7 -1
  5. package/dist/entities/system/Component.d.ts +480 -39
  6. package/dist/entities/system/Component.js +264 -93
  7. package/dist/entities/system/File.d.ts +1 -1
  8. package/dist/entities/system/LinkedSystem.d.ts +2 -2
  9. package/dist/entities/system/Operation.d.ts +1 -1
  10. package/dist/entities/system/StyleProperty.d.ts +8 -0
  11. package/dist/entities/system/StyleProperty.js +12 -1
  12. package/dist/entities/system/Tool.d.ts +1 -1
  13. package/dist/entities/system/component-module.js +1 -1
  14. package/dist/entities/system/defineComponent.d.ts +45 -7
  15. package/dist/entities/system/defineComponent.js +52 -25
  16. package/dist/framework/Config.d.ts +2 -2
  17. package/dist/framework/Config.js +59 -7
  18. package/dist/framework/class-names.d.ts +4 -2
  19. package/dist/framework/class-names.js +56 -17
  20. package/dist/framework/defineDerivedEntity.d.ts +1 -1
  21. package/dist/framework/defineEntity.d.ts +2 -2
  22. package/dist/framework/defineSubEntity.d.ts +3 -2
  23. package/dist/framework/preview-store.d.ts +19 -0
  24. package/dist/framework/preview-store.js +18 -0
  25. package/dist/framework/projections.d.ts +28 -8
  26. package/dist/framework/projections.js +50 -16
  27. package/dist/framework/prop-surface.d.ts +21 -1
  28. package/dist/framework/prop-surface.js +99 -21
  29. package/dist/framework/render-spec.d.ts +103 -3
  30. package/dist/framework/render-spec.js +285 -37
  31. package/dist/framework/schema-version.d.ts +1 -1
  32. package/dist/framework/schema-version.js +5 -5
  33. package/dist/framework/utils/refs.js +10 -1
  34. package/dist/framework/validate-spec.d.ts +2 -0
  35. package/dist/framework/validate-spec.js +13 -0
  36. package/dist/index.d.ts +14 -11
  37. package/dist/index.js +6 -5
  38. package/dist/migrations/2.0.0/v1-artifact.d.ts +305 -29
  39. package/dist/migrations/20260919051027_prop_conditions_and_anatomy_positions_at_rest.d.ts +22 -0
  40. package/dist/migrations/20260919051027_prop_conditions_and_anatomy_positions_at_rest.js +10 -0
  41. package/dist/migrations/20260920004019_preview_conditions_keyed.d.ts +27 -0
  42. package/dist/migrations/20260920004019_preview_conditions_keyed.js +148 -0
  43. package/dist/migrations/20260920161309_prefixed_token_group.d.ts +12 -0
  44. package/dist/migrations/20260920161309_prefixed_token_group.js +10 -0
  45. package/dist/renderer/wrapRegistry.js +8 -2
  46. package/dist/renderer/wrappers/component-refs.d.ts +14 -0
  47. package/dist/renderer/wrappers/component-refs.js +79 -0
  48. package/dist/renderer/wrappers/hex-normalize.d.ts +5 -7
  49. package/dist/renderer/wrappers/hex-normalize.js +11 -10
  50. package/dist/spec/cell.d.ts +171 -0
  51. package/dist/spec/cell.js +361 -0
  52. package/dist/spec/detach.d.ts +125 -0
  53. package/dist/spec/detach.js +511 -0
  54. package/dist/spec/empty-node-slots.d.ts +11 -23
  55. package/dist/spec/empty-node-slots.js +12 -39
  56. package/dist/spec/index.d.ts +5 -3
  57. package/dist/spec/index.js +3 -3
  58. package/dist/spec/instance.d.ts +77 -0
  59. package/dist/spec/instance.js +80 -0
  60. package/dist/spec/preview-content.d.ts +95 -0
  61. package/dist/spec/preview-content.js +149 -0
  62. package/dist/spec/preview-elements.d.ts +54 -15
  63. package/dist/spec/preview-elements.js +109 -29
  64. package/dist/spec/prop-conditions.d.ts +28 -0
  65. package/dist/spec/prop-conditions.js +39 -0
  66. package/dist/spec/slot.d.ts +206 -0
  67. package/dist/spec/slot.js +542 -0
  68. package/dist/spec/visibility.d.ts +82 -0
  69. package/dist/spec/visibility.js +109 -0
  70. package/dist/tsconfig.tsbuildinfo +1 -1
  71. package/package.json +18 -2
@@ -38,7 +38,7 @@ const componentsByFileCache = /* @__PURE__ */ new WeakMap();
38
38
  /** Documentation and examples never enter emitted component modules. */
39
39
  function buildEntityBody(kind, body) {
40
40
  if (kind !== "component") return body;
41
- const { previewProps: _preview, description: _description, ...buildBody } = body;
41
+ const { preview: _preview, description: _description, ...buildBody } = body;
42
42
  return buildBody;
43
43
  }
44
44
  /**
@@ -1,5 +1,5 @@
1
1
  import { Ref as Ref$1 } from "../../framework/utils/refs.js";
2
- import { RegisteredModifiers, RegisteredStyleProps } from "../../framework/registered.js";
2
+ import { RegisteredModifiers, RegisteredPaths, RegisteredStyleProps } from "../../framework/registered.js";
3
3
  import { Authored } from "../../framework/authoring.js";
4
4
  import { StyleValue } from "./style-bag.js";
5
5
  import { VariantValues } from "./Component.js";
@@ -73,8 +73,17 @@ type LayerPath<L extends LayerMap = LayerMap> = `${keyof L & string}/${string}`;
73
73
  * from anything else: a widened union left a slot prop inferring as `unknown` where the same prop
74
74
  * written by hand stayed a `ReactNode`.
75
75
  */
76
+ /**
77
+ * When a prop applies — the record a style rule's `when` holds: each key a sibling prop, each value
78
+ * one of that prop's values or a list of them. A prop with no condition always applies. Stored with
79
+ * the values as refs (`value:type/color`), which is what lets a value rename cascade into it.
80
+ */
81
+ type PropCondition = Readonly<Record<string, ConditionValue | ConditionValue[]>>;
82
+ /** A value as authored, or the `value:` ref it is stored as. */
83
+ type ConditionValue = string | number | boolean | Ref$1;
76
84
  interface SlotDecl {
77
85
  type: 'slot';
86
+ when?: PropCondition;
78
87
  /**
79
88
  * What may FILL this hole, as refs. Two shapes, both in this repo:
80
89
  *
@@ -131,6 +140,7 @@ interface ForwardDecl<Path extends string = string> {
131
140
  accepts?: Ref$1 | Ref$1[];
132
141
  text?: boolean;
133
142
  required?: boolean;
143
+ when?: PropCondition;
134
144
  }
135
145
  /**
136
146
  * One prop's declaration, in both the form it is AUTHORED in and the form it is STORED in.
@@ -154,20 +164,25 @@ type PropDecl<Path extends string = string> = SlotDecl | ForwardDecl<Path> | {
154
164
  type: 'variant';
155
165
  value: readonly string[] | VariantValues | Ref$1;
156
166
  required?: boolean;
167
+ when?: PropCondition;
157
168
  } | {
158
169
  type: 'styleProperty';
159
170
  value: Ref$1;
160
171
  required?: boolean;
172
+ when?: PropCondition;
161
173
  } | {
162
174
  type: 'composite';
163
175
  value: Ref$1;
164
176
  required?: boolean;
177
+ when?: PropCondition;
165
178
  } | {
166
179
  type: 'motion';
167
180
  required?: boolean;
181
+ when?: PropCondition;
168
182
  } | {
169
183
  type: 'string' | 'number' | 'boolean';
170
184
  required?: boolean;
185
+ when?: PropCondition;
171
186
  };
172
187
  /**
173
188
  * One prop as AUTHORED — a declaration, or one of the two bare forms that already say what they are:
@@ -228,6 +243,13 @@ type ValueOf<D> = D extends readonly (infer Member)[] ? Member : D extends {
228
243
  __ref: `style-property:${infer Target}`;
229
244
  };
230
245
  } ? StylePropValue<Target> : ValueOfDeclaration<D>;
246
+ /**
247
+ * The members a ref-delegated domain offers, read off the registered paths: a variant whose domain
248
+ * is `icon:phosphor` takes every registered `phosphor/<Name>` as `<Name>`, so a misspelled glyph is
249
+ * a compile error once the system is built. A facet ref (`icon:phosphor#variants`), a kind the build
250
+ * did not register, or an unbuilt consumer stays `string`.
251
+ */
252
+ type RegisteredMembers<Kind extends string, Path extends string> = Path extends `${string}#${string}` ? string : Kind extends keyof RegisteredPaths ? [Extract<RegisteredPaths[Kind], `${Path}/${string}`>] extends [never] ? string : Extract<RegisteredPaths[Kind], `${Path}/${string}`> extends `${Path}/${infer Name}` ? Name : string : string;
231
253
  /**
232
254
  * A style prop's values, read off the registry by the path the declaration points at. By the target
233
255
  * and not the prop's own name, since a component may expose a property under a name of its own —
@@ -240,7 +262,9 @@ type StylePropValue<Target extends string> = Target extends keyof RegisteredStyl
240
262
  type ValueOfDeclaration<D> = D extends {
241
263
  type: 'variant';
242
264
  value: infer V;
243
- } ? V extends readonly (infer Member)[] ? Member : V extends VariantValues ? keyof V & string : string : D extends {
265
+ } ? V extends readonly (infer Member)[] ? Member : V extends VariantValues ? keyof V & string : V extends {
266
+ __ref: `${infer Kind}:${infer Path}`;
267
+ } ? RegisteredMembers<Kind, Path> : string : D extends {
244
268
  type: 'string';
245
269
  } ? string : D extends {
246
270
  type: 'number';
@@ -267,7 +291,21 @@ type ValueOfDeclaration<D> = D extends {
267
291
  type Declared<T> = { [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K] };
268
292
  /** What a prop map really exposes: each declaration's value type, with the `null` tombstones — the
269
293
  * props it DROPS from what it inherits — gone from the surface entirely. */
270
- type DeclaredProps<P> = { [K in keyof P as P[K] extends null ? never : K]: ValueOf<P[K]> };
294
+ type DeclaredProps<P, L extends LayerMap = LayerMap> = { [K in keyof P as P[K] extends null ? never : K]: RoutedValueOf<L, P[K]> };
295
+ /**
296
+ * A forward's consumer type is the far prop's: a route onto a layer component's `name` takes what
297
+ * that component's `name` takes, and a route onto `<input disabled>` takes a boolean. The layers
298
+ * are in scope here, which is what {@link ValueOf} lacks. A text forward is a string whatever it
299
+ * lands on. A route onto `children`, or onto a layer the map doesn't hold (an open `LayerMap`, as in
300
+ * a defaults bag), keeps `ValueOf`'s answer.
301
+ */
302
+ type RoutedValueOf<L extends LayerMap, D> = D extends {
303
+ type: 'forward';
304
+ text: true;
305
+ } ? string : D extends {
306
+ type: 'forward';
307
+ target: `${infer Layer}/${infer Prop}`;
308
+ } ? Prop extends 'children' ? ReactNode : Layer extends keyof L ? Prop extends keyof ElementPropsOf<L[Layer]> ? ElementPropsOf<L[Layer]>[Prop] : unknown : unknown : ValueOf<D>;
271
309
  /**
272
310
  * The props the rendered ELEMENT contributes, `ref` included.
273
311
  *
@@ -348,7 +386,7 @@ type RequiredKey<P, D> = { [K in keyof P]: P[K] extends {
348
386
  * is checked against its interface by relating two generic signatures, and a name type that reads
349
387
  * `P` is not identical on the two sides of that comparison — a name type reading only `E` is.
350
388
  */
351
- type ConsumerDeclaredProps<P, D, E> = Without<Partial<DeclaredProps<P>>, Redefined<E>> & { [K in Exclude<RequiredKey<P, D>, Redefined<E>>]-?: K extends keyof DeclaredProps<P> ? DeclaredProps<P>[K] : never };
389
+ type ConsumerDeclaredProps<P, D, E, L extends LayerMap = LayerMap> = Without<Partial<DeclaredProps<P, L>>, Redefined<E>> & { [K in Exclude<RequiredKey<P, D>, Redefined<E>>]-?: K extends keyof DeclaredProps<P, L> ? DeclaredProps<P, L>[K] : never };
352
390
  /**
353
391
  * The keys a render extra redefines: the ones it names. An open extra (`extends Record<string,
354
392
  * unknown>`) names nothing through its index signature, but a key it declares beside that signature
@@ -395,7 +433,7 @@ type OpenTail<L extends LayerMap> = Registered extends true ? StyleProps & Modif
395
433
  *
396
434
  * Ends in {@link OpenTail}: the system's own surface, open until the system's build has named it.
397
435
  */
398
- type ComponentPropsOf<L extends LayerMap, P extends PropMap, D = Record<never, never>, E = Record<never, never>, F extends string = never, As = never> = ConsumerDeclaredProps<P, D, E> & Partial<E> & Redeclared<RootSurface<L, D, As>, P, E> & Redeclared<ForwardedPropsOf<L, F>, P, E> & AsPropsOf<As, P, E> & Redeclared<OpenTail<L>, P, E>;
436
+ type ComponentPropsOf<L extends LayerMap, P extends PropMap, D = Record<never, never>, E = Record<never, never>, F extends string = never, As = never> = ConsumerDeclaredProps<P, D, E, L> & Partial<E> & Redeclared<RootSurface<L, D, As>, P, E> & Redeclared<ForwardedPropsOf<L, F>, P, E> & AsPropsOf<As, P, E> & Redeclared<OpenTail<L>, P, E>;
399
437
  /**
400
438
  * `T` less the keys the component declares and the keys its render extra names — each taken out on
401
439
  * its own, because {@link Without} leaves `T` whole when the key set is open, and an open render
@@ -502,7 +540,7 @@ type RenderArgs<L extends LayerMap, P extends PropMap> = {
502
540
  * `<input ref={ref}>` in another — and which element is the render's decision.
503
541
  */
504
542
  readonly ref?: Ref<any>;
505
- } & DeclaredProps<P> & Record<string, unknown>;
543
+ } & DeclaredProps<P, L> & Record<string, unknown>;
506
544
  interface ComponentBuilder<L extends LayerMap, P extends PropMap,
507
545
  /** Props the RENDER takes that the config doesn't describe — React handlers, mostly (`onPress`).
508
546
  * They're part of the component's surface but not of its data, which is why they're declared at
@@ -652,4 +690,4 @@ interface DefineComponent {
652
690
  declare function definePropsFirst<const P extends PropMap<LayerPath>>(props: P & NoInfer<{ [K in keyof P]: Exact<P[K], AuthoredProp<LayerPath>> }>): ComponentBuilder<LayerMap, P>;
653
691
  declare const defineComponent: DefineComponent;
654
692
  //#endregion
655
- export { AsInput, AuthoredComponent, AuthoredProp, ComponentBuilder, ComponentPropsOf, LayerElement, LayerMap, PhantomCallProps, PhantomReadProps, PropDecl, PropMap, RenderArgs, StyleRuleInput, defineComponent };
693
+ export { AsInput, AuthoredComponent, AuthoredProp, ComponentBuilder, ComponentPropsOf, LayerElement, LayerMap, PhantomCallProps, PhantomReadProps, PropCondition, PropDecl, PropMap, RegisteredMembers, RenderArgs, StyleRuleInput, defineComponent };
@@ -1,4 +1,5 @@
1
- import { isRef, kindOf, memberRef, pathOf } from "../../framework/utils/refs.js";
1
+ import { canonicalCondition, isRef, kindOf, memberRef, pathOf } from "../../framework/utils/refs.js";
2
+ import "../../framework/preview-store.js";
2
3
  import { Component, canonicalWhen, layerRef } from "./Component.js";
3
4
  //#region src/entities/system/defineComponent.ts
4
5
  function build(body) {
@@ -20,10 +21,8 @@ function build(body) {
20
21
  render: () => handle,
21
22
  renderCanvas: () => handle,
22
23
  preview: (spec) => {
23
- const patch = {};
24
- if (spec?.defaultProps !== void 0) patch.previewProps = spec.defaultProps;
25
- if (spec?.when?.length) patch.previewWhen = spec.when;
26
- return Object.keys(patch).length === 0 ? handle : next(patch);
24
+ if (spec?.defaultProps === void 0 && !spec?.when?.length) return handle;
25
+ return next({ preview: spec });
27
26
  },
28
27
  toStored: () => toStored(body),
29
28
  assignPath(path) {
@@ -45,12 +44,15 @@ function build(body) {
45
44
  * only one of them runs for any given config: the build reads `.preview()` from source with ts-morph,
46
45
  * this runs when a config is registered, and a reader can't tell which made the config it holds.
47
46
  *
48
- * What this path CANNOT do is name an element the source could. A tag is its own name and a component
49
- * handle carries its adopted path, but a foreign function (a Phosphor glyph, say) is just a function
50
- * at runtime — its `displayName` is a label, not an identity, and two libraries may both export
51
- * `StarIcon`. Such a prop is DROPPED: an absent default renders nothing, while a guessed ref renders
52
- * the wrong glyph and reads as authored. The build resolves those from the import specifier, which is
53
- * why ~a third of the element-valued preview props in a real system only survive a build.
47
+ * A component handed as a VALUE (`icon: PlusIcon`) stores as the bare ref the registry keys it under,
48
+ * so the receiving component gets the component back rather than a node.
49
+ *
50
+ * What this path CANNOT do is name a component the source could. A tag is its own name and a
51
+ * component handle carries its adopted path, but a foreign function (a Phosphor glyph, say) is just
52
+ * a function at runtime — its `displayName` is a label, not an identity, and two libraries may both
53
+ * export `StarIcon`. Such a prop is DROPPED: an absent default renders nothing, while a guessed ref
54
+ * renders the wrong glyph and reads as authored. The build resolves those from the import specifier,
55
+ * which is why ~a third of the element-valued preview props in a real system only survive a build.
54
56
  */
55
57
  function storableProps(props) {
56
58
  if (props === void 0) return void 0;
@@ -61,9 +63,19 @@ function storableProps(props) {
61
63
  }
62
64
  return Object.keys(out).length === 0 ? void 0 : out;
63
65
  }
64
- /** A live React element, by the marker anything pretending to be one also carries. */
66
+ /** A live React element, by the marker anything pretending to be one also carries. A `forwardRef` or
67
+ * `memo` component carries the marker too and is told apart by having no `props`: it is a type. */
65
68
  function isReactElement(value) {
66
- return typeof value === "object" && value !== null && "$$typeof" in value;
69
+ return typeof value === "object" && value !== null && "$$typeof" in value && "props" in value;
70
+ }
71
+ /** A component TYPE handed as a value: a function, or the object `forwardRef` / `memo` return. */
72
+ function isComponentType(value) {
73
+ if (typeof value === "function") return true;
74
+ return typeof value === "object" && value !== null && "$$typeof" in value && !("props" in value);
75
+ }
76
+ /** A component handed as a value, as the ref that names it — `undefined` for one this path can't name. */
77
+ function storableComponentRef(value) {
78
+ return isComponentElement(value) && value.path ? Component.ref(value.path) : void 0;
67
79
  }
68
80
  const FRAGMENT = Symbol.for("react.fragment");
69
81
  /**
@@ -74,12 +86,20 @@ function storableElementName(type) {
74
86
  if (typeof type === "string") return type;
75
87
  if (isComponentElement(type) && type.path) return Component.ref(type.path);
76
88
  }
77
- /** The scalar half of an element's props — what `PreviewElement.props` may hold. */
89
+ /** The scalar half of an element's props, plus a component handed as one — what
90
+ * `PreviewElement.props` may hold. */
78
91
  function storableAttributes(props) {
79
92
  const out = {};
80
93
  for (const [key, value] of Object.entries(props)) {
81
94
  if (key === "children") continue;
82
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") out[key] = value;
95
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
96
+ out[key] = value;
97
+ continue;
98
+ }
99
+ if (isComponentType(value)) {
100
+ const ref = storableComponentRef(value);
101
+ if (ref) out[key] = ref;
102
+ }
83
103
  }
84
104
  return out;
85
105
  }
@@ -118,13 +138,14 @@ function storableElement(value) {
118
138
  /**
119
139
  * One preview-prop value as the config stores it, or `undefined` to drop the prop.
120
140
  *
121
- * Mirrors the arms the build reads from source: a literal, an element, a fragment (several elements
122
- * under one prop), an array, and a nested record.
141
+ * Mirrors the arms the build reads from source: a literal, a component, an element, a fragment
142
+ * (several elements under one prop), an array, and a nested record.
123
143
  */
124
144
  function storableValue(value) {
125
145
  if (value === null) return null;
126
146
  const type = typeof value;
127
147
  if (type === "string" || type === "number" || type === "boolean") return value;
148
+ if (isComponentType(value)) return storableComponentRef(value);
128
149
  if (type !== "object") return void 0;
129
150
  if (isReactElement(value)) {
130
151
  if (value.type === FRAGMENT) {
@@ -299,15 +320,21 @@ function storedForwards(forwards, layers) {
299
320
  /** The authored body as the entity's stored shape — which differs in exactly four places. */
300
321
  function toStored(body) {
301
322
  const stored = { ...body };
302
- if (body.previewProps !== void 0) {
303
- const previewProps = storableProps(body.previewProps);
304
- if (previewProps === void 0) delete stored.previewProps;
305
- else stored.previewProps = previewProps;
323
+ if (body.preview !== void 0) {
324
+ const authored = body.preview;
325
+ const keyed = {};
326
+ const defaults = authored.defaultProps !== void 0 ? storableProps(authored.defaultProps) : void 0;
327
+ if (defaults !== void 0) keyed["*"] = {
328
+ when: null,
329
+ props: defaults
330
+ };
331
+ for (const { when, props } of authored.when ?? []) keyed[canonicalCondition(when)] = {
332
+ when,
333
+ props: storableProps(props) ?? {}
334
+ };
335
+ if (Object.keys(keyed).length === 0) delete stored.preview;
336
+ else stored.preview = keyed;
306
337
  }
307
- if (Array.isArray(body.previewWhen)) stored.previewWhen = body.previewWhen.map(({ when, props }) => ({
308
- when,
309
- props: storableProps(props) ?? {}
310
- }));
311
338
  if (body.props && typeof body.props === "object") {
312
339
  const props = {};
313
340
  for (const [name, decl] of Object.entries(body.props)) props[name] = storedProp(decl, body.layers);
@@ -8,12 +8,12 @@ import { RefGraph, TraverseOptions } from "./ref-graph.js";
8
8
  import { FieldsSchema } from "./utils/field-schema.js";
9
9
  import { SubEntityClass } from "./defineSubEntity.js";
10
10
  import { LocalOverlay, SourceAnswer, SourceOverlay, SourceResolver, SourceState } from "./sources.js";
11
+ import { EntityKind } from "./registry.js";
11
12
  import { AuthoredEntry } from "./utils/group.js";
12
13
  import { EntityClass } from "./defineEntity.js";
13
14
  import { BreakPlan, LinkPlan, SourceRowState, UnlinkPlan } from "../entities/system/link-plan.js";
14
15
  import { InlineOptions } from "./inline-sources.js";
15
16
  import { Snapshot } from "./snapshot.js";
16
- import { EntityKind } from "./registry.js";
17
17
 
18
18
  //#region src/framework/Config.d.ts
19
19
  /**
@@ -72,7 +72,7 @@ interface RecordedSource {
72
72
  * The dense-counter `2` a short-lived scheme stamped before the chain existed reads as the mint
73
73
  * that replaced it (see `detectedWireVersion`).
74
74
  */
75
- declare const SERIALIZED_CONFIG_VERSION = 20260918161049;
75
+ declare const SERIALIZED_CONFIG_VERSION = 20260920161309;
76
76
  interface SerializedConfig {
77
77
  /** See {@link SERIALIZED_CONFIG_VERSION}. Always written; a stored artifact may lack it. */
78
78
  readonly version: number;
@@ -367,31 +367,82 @@ function dropOrphans(sub, def, target, namespace) {
367
367
  }
368
368
  /**
369
369
  * Follow a member's name where the item holds it as a value keyed by that member's owner — a
370
- * Component's `defaultProps` and `previewProps`, which are both `{ [prop]: value }`.
370
+ * Component's `defaultProps`, and the `props` of every `preview` entry, which are all
371
+ * `{ [prop]: value }`.
371
372
  *
372
373
  * The third rename axis, after refs and record keys, and the only one where the name appears as
373
374
  * neither. `to: undefined` is the delete case: a default naming a value that no longer exists isn't
374
375
  * a dangling pointer anyone sees, it's a component that quietly reports a default it hasn't got.
376
+ * A field is a `/` path, and a `*` segment stands for every key at that level, as in
377
+ * {@link rekeyAt}.
375
378
  */
376
379
  function renameNamedValues(sub, def, owner, from, to) {
377
380
  if (!sub?.namedValuesIn?.length || owner === void 0) return def;
378
381
  let out = def;
379
382
  for (const field of sub.namedValuesIn) {
380
- const record = out[field];
381
- if (!isPlainObject(record) || record[owner] !== from) continue;
383
+ const next = renameValueAt(out, field.split("/"), owner, from, to);
384
+ if (isPlainObject(next)) out = next;
385
+ }
386
+ return out;
387
+ }
388
+ /** Rewrite the value under `owner` in the record `path` addresses, from `from` to `to` (or drop it). */
389
+ function renameValueAt(node, path, owner, from, to) {
390
+ if (!isPlainObject(node)) return node;
391
+ const [head, ...rest] = path;
392
+ if (head === void 0) return node;
393
+ if (rest.length === 0) {
394
+ const record = node[head];
395
+ if (!isPlainObject(record) || record[owner] !== from) return node;
382
396
  const next = { ...record };
383
397
  if (to === void 0) delete next[owner];
384
398
  else next[owner] = to;
385
- out = {
399
+ return {
400
+ ...node,
401
+ [head]: next
402
+ };
403
+ }
404
+ if (head === "*") {
405
+ let changed = false;
406
+ const next = {};
407
+ for (const [key, value] of Object.entries(node)) {
408
+ const updated = renameValueAt(value, rest, owner, from, to);
409
+ next[key] = updated;
410
+ if (updated !== value) changed = true;
411
+ }
412
+ return changed ? next : node;
413
+ }
414
+ const updated = renameValueAt(node[head], rest, owner, from, to);
415
+ return updated === node[head] ? node : {
416
+ ...node,
417
+ [head]: updated
418
+ };
419
+ }
420
+ /**
421
+ * Drop the members a name removal left declaring nothing. A `keyedByNameIn` path through another
422
+ * collection (`preview/*\/props`) removes a key from each of that collection's members, and a member
423
+ * that held only that key now says nothing; the collection's own `vacant` decides, the same test a
424
+ * `sub-update` asks before bringing one into being.
425
+ */
426
+ function dropVacated(entry, sub, def) {
427
+ if (!sub?.keyedByNameIn?.length) return def;
428
+ let out = def;
429
+ for (const path of sub.keyedByNameIn) {
430
+ const [field, wildcard] = path.split("/");
431
+ if (wildcard !== "*") continue;
432
+ const collectionSub = entry.subEntities[field];
433
+ const collection = out[field];
434
+ if (!collectionSub?.vacant || !isPlainObject(collection)) continue;
435
+ const kept = Object.fromEntries(Object.entries(collection).filter(([, member]) => collectionSub.vacant?.(member) !== true));
436
+ if (Object.keys(kept).length !== Object.keys(collection).length) out = {
386
437
  ...out,
387
- [field]: next
438
+ [field]: kept
388
439
  };
389
440
  }
390
441
  return out;
391
442
  }
392
443
  /**
393
444
  * Follow a member's name where the item holds it as a record key — a component's `defaultProps` and
394
- * `previewProps` are both `{ [prop]: value }`.
445
+ * every `preview` entry's `props` are `{ [prop]: value }`.
395
446
  *
396
447
  * The other half of {@link renameNamedValues}, which reaches the same two fields from the value side.
397
448
  * `to: undefined` is the delete case, and it matters as much as the rename: a default left under the
@@ -1669,6 +1720,7 @@ var Config = class Config {
1669
1720
  def = store(def, collection);
1670
1721
  def = renameNamedValues(sub, def, patch.owner, patch.key, void 0);
1671
1722
  def = renameKeyedByName(sub, def, patch.key, void 0);
1723
+ def = dropVacated(entry, sub, def);
1672
1724
  def = dropOrphans(sub, def, `${leafOf(patch.field).replace(/s$/, "")}:${refPathOf(patch, patch.key)}`, sub?.namespaces ? {
1673
1725
  kind: sub.namespaces,
1674
1726
  path: patch.key
@@ -4739,7 +4791,7 @@ var Config = class Config {
4739
4791
  * links from the provided sources. A declared `linkedKind` with no provided source throws. */
4740
4792
  hydrateFrom(stored, options) {
4741
4793
  const claimed = schemaVersionOf(stored);
4742
- if (claimed > 20260918161049) throw new SchemaVersionTooNew(claimed);
4794
+ if (claimed > 20260920161309) throw new SchemaVersionTooNew(claimed);
4743
4795
  const wire = upgradeSerializedConfig(stored);
4744
4796
  if (!Array.isArray(wire.ownedKinds) || typeof wire.items !== "object" || wire.items === null) throw new ConfigFormatError(`Config "${stored.name ?? "system-config"}" cannot be hydrated: this is not a serialized config-v2 config (no "ownedKinds"/"items"). A pre-cutover build artifact converts through the registered cutover migration — import the config type's module (e.g. \`configs/system\`) before hydrating, or port the repo once with \`uds migrate\`.`);
4745
4797
  const leftover = wire.options;
@@ -150,8 +150,10 @@ declare function componentPropClasses(config: Config, path: string, state?: Reco
150
150
  * travels to it as one, whether or not the element declares it: a composite (`variant` on a
151
151
  * `Text`-backed layer, which the receiver resolves to its own class), the receiver's own variant or
152
152
  * scalar prop (`size: 'sm'` on an Icon layer), or an attribute of a plain element (`as: 'div'`,
153
- * `type: 'button'`, `aria-hidden`). A receiver prop with a channel of its own — a style property
154
- * under another name, a slot, a forward, motion — stays out, since that channel already carries it.
153
+ * `type: 'button'`, `aria-hidden`), or a value the rule hands a receiver's forward or slot
154
+ * (`children: 'AI'` on a badge layer, `icon: 'XIcon'` on a button layer), which the emitted module
155
+ * routes the same way. A style property under another name and motion stay out, since the
156
+ * stylesheet and the motion classes already carry them.
155
157
  *
156
158
  * Forwarding rather than resolving is what makes a layer's styling live-editable at all: a class only
157
159
  * exists once a build has emitted the stylesheet, so an edit that makes a NEW style rule paints
@@ -1,7 +1,8 @@
1
- import { isRef, kindOf, pathOf, refLeaf } from "./utils/refs.js";
2
- import { rootLayerOf } from "../entities/system/element.js";
1
+ import { isRef, kindOf, memberOf, pathOf, refLeaf } from "./utils/refs.js";
2
+ import { forwardsOf, rootLayerOf } from "../entities/system/element.js";
3
3
  import { classPrefixOf } from "../entities/system/Settings.js";
4
4
  import { createSliceMemo } from "./memo.js";
5
+ import { storedPreview } from "./preview-store.js";
5
6
  import { authoredValue, className, negatedCssValue, renderStyleValue, resolveComponentProps, stylePropLeafForToken, stylePropValueLeaves, stylePropertyPathFor } from "./projections.js";
6
7
  import { ruleApplies } from "./layer-styles.js";
7
8
  //#region src/framework/class-names.ts
@@ -295,38 +296,74 @@ function componentMotionClasses(config, path) {
295
296
  function componentPropClasses(config, path, state = {}) {
296
297
  const declared = compositePropsOf(config, path);
297
298
  if (!declared) return {};
298
- const { root, groups, defaultProps } = declared;
299
+ const { groups, defaultProps } = declared;
299
300
  if (groups.length === 0) return {};
300
301
  const resolved = {
301
302
  ...defaultProps,
302
303
  ...state
303
304
  };
304
- const classes = [];
305
- for (const [prop, group] of groups) {
305
+ const out = {};
306
+ for (const [prop, group, layer] of groups) {
306
307
  const value = resolved[prop];
307
308
  const target = memberNamed(group, value);
308
309
  if (target === void 0) continue;
309
310
  if (!config.resolve("composite", target)) continue;
311
+ const classes = out[layer] ?? [];
310
312
  classes.push(className(config, "composite", target));
313
+ out[layer] = classes;
311
314
  }
312
- return classes.length > 0 ? { [root]: classes } : {};
315
+ return out;
313
316
  }
314
317
  /** The part of {@link componentPropClasses} that depends on the component alone, once per slice:
315
318
  * an expansion asks it for every instance it paints. `undefined` for a path no component answers. */
316
- function compositePropsOf(config, path) {
319
+ function compositePropsOf(config, path, seen = /* @__PURE__ */ new Set()) {
317
320
  const cache = compositePropsCache(config);
318
321
  if (cache.has(path)) return cache.get(path);
319
322
  const component = config.resolve("component", path);
320
323
  let declared;
321
324
  if (component) {
322
- const { layers = {}, props = {}, defaultProps = {} } = component.toJSON();
325
+ const body = component.toJSON();
326
+ const { layers = {}, props = {} } = body;
323
327
  const [root] = rootLayerOf(layers);
324
328
  const groups = [];
329
+ let defaultProps = {};
325
330
  for (const [prop, decl] of Object.entries(props)) {
326
331
  if (decl?.type !== "composite") continue;
327
332
  const group = isRef(decl.value) ? decl.value.__ref : void 0;
328
- if (group) groups.push([prop, group]);
333
+ if (group) groups.push([
334
+ prop,
335
+ group,
336
+ root
337
+ ]);
329
338
  }
339
+ const inner = new Set([...seen, path]);
340
+ for (const [layer, entry] of Object.entries(forwardsOf(body))) {
341
+ const element = layers[layer];
342
+ if (!isRef(element) || kindOf(element.__ref) !== "component") continue;
343
+ const from = pathOf(element.__ref);
344
+ if (inner.has(from)) continue;
345
+ const exposed = compositePropsOf(config, from, inner);
346
+ if (!exposed) continue;
347
+ const names = Array.isArray(entry) ? new Set(entry.flatMap((r) => isRef(r) ? [memberOf(r.__ref)?.split("/").pop()] : [])) : void 0;
348
+ for (const [prop, group, at] of exposed.groups) {
349
+ if (at !== exposed.root) continue;
350
+ if (prop in props) continue;
351
+ if (entry !== "*" && !names?.has(prop)) continue;
352
+ groups.push([
353
+ prop,
354
+ group,
355
+ layer
356
+ ]);
357
+ }
358
+ defaultProps = {
359
+ ...exposed.defaultProps,
360
+ ...defaultProps
361
+ };
362
+ }
363
+ defaultProps = {
364
+ ...defaultProps,
365
+ ...body.defaultProps
366
+ };
330
367
  declared = {
331
368
  root,
332
369
  groups,
@@ -349,8 +386,10 @@ const compositePropsCache = createSliceMemo({
349
386
  * travels to it as one, whether or not the element declares it: a composite (`variant` on a
350
387
  * `Text`-backed layer, which the receiver resolves to its own class), the receiver's own variant or
351
388
  * scalar prop (`size: 'sm'` on an Icon layer), or an attribute of a plain element (`as: 'div'`,
352
- * `type: 'button'`, `aria-hidden`). A receiver prop with a channel of its own — a style property
353
- * under another name, a slot, a forward, motion — stays out, since that channel already carries it.
389
+ * `type: 'button'`, `aria-hidden`), or a value the rule hands a receiver's forward or slot
390
+ * (`children: 'AI'` on a badge layer, `icon: 'XIcon'` on a button layer), which the emitted module
391
+ * routes the same way. A style property under another name and motion stay out, since the
392
+ * stylesheet and the motion classes already carry them.
354
393
  *
355
394
  * Forwarding rather than resolving is what makes a layer's styling live-editable at all: a class only
356
395
  * exists once a build has emitted the stylesheet, so an edit that makes a NEW style rule paints
@@ -377,7 +416,7 @@ function layerRoutedProps(config, path, state = {}) {
377
416
  }) ?? prop;
378
417
  if (config.resolve("style-property", styleKey)) continue;
379
418
  const decl = declared[prop];
380
- if (decl?.type === "styleProperty" || decl?.type === "slot" || decl?.type === "forward" || decl?.type === "motion") continue;
419
+ if (decl?.type === "styleProperty" || decl?.type === "motion") continue;
381
420
  out[layer] ??= {};
382
421
  out[layer][prop] = authoredValue(config, prop, value);
383
422
  }
@@ -598,7 +637,7 @@ function modifierUtilitiesUsed(config) {
598
637
  /**
599
638
  * Every style bag a COMPONENT states it wants, each with the definitions its keys resolve against.
600
639
  *
601
- * Not uniform, which is the whole reason it travels with the bag. `defaultProps` and `previewProps`
640
+ * Not uniform, which is the whole reason it travels with the bag. `defaultProps` and a preview entry's `props`
602
641
  * are the component's OWN props, and nothing else has a claim. A style rule's per-layer bag and an
603
642
  * anatomy node's props are set on a LAYER'S ELEMENT, so that element answers too — after the
604
643
  * component itself, whose own declaration of a name is what that name means throughout its
@@ -606,14 +645,14 @@ function modifierUtilitiesUsed(config) {
606
645
  */
607
646
  function componentBags(entity) {
608
647
  const { path } = entity;
609
- const { defaultProps, previewProps, styles, anatomy, layers } = entity.toJSON();
648
+ const { defaultProps, styles, anatomy, layers } = entity.toJSON();
610
649
  const bags = [{
611
650
  bag: defaultProps,
612
651
  through: [path]
613
- }, {
614
- bag: previewProps,
652
+ }, ...Object.values(storedPreview(entity.toJSON())).map((entry) => ({
653
+ bag: entry?.props,
615
654
  through: [path]
616
- }];
655
+ }))];
617
656
  for (const rule of Object.values(styles ?? {})) for (const [layer, bag] of Object.entries(rule?.layers ?? {})) bags.push({
618
657
  bag,
619
658
  through: [path, componentElementOf(layers?.[layer])]
@@ -2,8 +2,8 @@ import { Entity } from "./Entity.js";
2
2
  import { Ref } from "./utils/refs.js";
3
3
  import { TraverseOptions } from "./ref-graph.js";
4
4
  import { FieldsSchema } from "./utils/field-schema.js";
5
- import { Config } from "./Config.js";
6
5
  import { ComputedInput, EntityKind } from "./registry.js";
6
+ import { Config } from "./Config.js";
7
7
  import { z } from "zod";
8
8
 
9
9
  //#region src/framework/defineDerivedEntity.d.ts
@@ -4,11 +4,11 @@ import { TraverseOptions } from "./ref-graph.js";
4
4
  import { CreateInput, FieldsSchema } from "./utils/field-schema.js";
5
5
  import { FieldsCtx, UserSchemas } from "./schemas.js";
6
6
  import { SubEntityClass } from "./defineSubEntity.js";
7
- import { PathOf } from "./registered.js";
8
7
  import { BulkOverlayWrite, BulkQuery, SourceOverlay, SourceResolutionSchemas } from "./sources.js";
8
+ import { ComputedFields, EntityKind, Linkable } from "./registry.js";
9
+ import { PathOf } from "./registered.js";
9
10
  import { GroupBody } from "./utils/group.js";
10
11
  import { Config, ListOptions, TreeNode } from "./Config.js";
11
- import { ComputedFields, EntityKind, Linkable } from "./registry.js";
12
12
  import { z } from "zod";
13
13
 
14
14
  //#region src/framework/defineEntity.d.ts
@@ -71,7 +71,8 @@ type SubEntityClass<F extends z.ZodTypeAny = z.ZodTypeAny> = CollectionSchema<F>
71
71
  readonly subEntities?: Readonly<Record<string, SubEntityClass>>;
72
72
  /**
73
73
  * Fields of the OWNING ENTITY that hold this member's NAME as a VALUE, keyed by the member's
74
- * owner — a component's `defaultProps` and `previewProps` are both `{ [prop]: value }`.
74
+ * owner — a component's `defaultProps` and each `preview` entry's `props` are `{ [prop]: value }`.
75
+ * A field is a `/` path; `*` stands for every key at that level.
75
76
  *
76
77
  * The mirror of {@link namesMembersOf}, which covers a name held as a record KEY. Neither is
77
78
  * reachable by a ref rewrite, and both leave the same kind of wreckage: a default naming a
@@ -81,7 +82,7 @@ type SubEntityClass<F extends z.ZodTypeAny = z.ZodTypeAny> = CollectionSchema<F>
81
82
  readonly namedValuesIn?: readonly string[];
82
83
  /**
83
84
  * Fields of the OWNING ENTITY that are KEYED by this member's name — a component's
84
- * `defaultProps` and `previewProps` are both `{ [prop]: value }`, so the prop's name is the key
85
+ * `defaultProps` and each `preview` entry's `props` are `{ [prop]: value }`, so the prop's name is the key
85
86
  * rather than the value.
86
87
  *
87
88
  * The other half of {@link namedValuesIn}, which covers the same two fields from the value side
@@ -0,0 +1,19 @@
1
+ //#region src/framework/preview-store.d.ts
2
+ /**
3
+ * A component's stored `preview` collection, read off a body with nothing else in scope: the render
4
+ * spec builder and the class scanner read it without reaching the projections module.
5
+ */
6
+ /** One `preview` entry as stored: the condition it holds under, `null` for the `*` entry every
7
+ * cell reads, and the props the matching cells preview with. */
8
+ interface StoredPreviewCondition {
9
+ readonly when: Record<string, unknown> | null;
10
+ readonly props: Record<string, unknown>;
11
+ }
12
+ /** The `*` entry the `preview` collection stores — see `preview({ defaultProps })`. */
13
+ declare const UNCONDITIONAL_PREVIEW_KEY = "*";
14
+ /** Every `preview` entry a body stores, keyed as stored. Empty for a body that stores none. */
15
+ declare function storedPreview(body: unknown): Readonly<Record<string, StoredPreviewCondition>>;
16
+ /** The props the `*` entry previews with, `undefined` when the body stores no such entry. */
17
+ declare function unconditionalPreviewProps(body: unknown): Record<string, unknown> | undefined;
18
+ //#endregion
19
+ export { StoredPreviewCondition, UNCONDITIONAL_PREVIEW_KEY, storedPreview, unconditionalPreviewProps };