@yahoo/uds-create-config 5.68.1 → 5.68.3

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.
@@ -15,8 +15,8 @@ declare const DocumentationDefinition: EntityClass<z.ZodObject<{
15
15
  uds: "uds";
16
16
  }>>;
17
17
  platform: z.ZodOptional<z.ZodEnum<{
18
- mobile: "mobile";
19
18
  web: "web";
19
+ mobile: "mobile";
20
20
  }>>;
21
21
  }, z.core.$strict>, z.ZodObject<Record<never, never>, z.core.$strip>, "documentation", {
22
22
  readonly search: {
@@ -14,9 +14,8 @@ import { AllHTMLAttributes, ComponentPropsWithRef, ComponentType, ElementType, J
14
14
  * Enough of another component to BE a layer's element — the handle `defineComponent` returns, with
15
15
  * its declarations forgotten so a builder of any shape fits.
16
16
  *
17
- * Named separately from `AuthoredComponent` because that one fixes the declarations at their widest,
18
- * and a layer must keep the ones the referenced component actually made: they're where its props come
19
- * from. `ComponentBuilder<{root:'button'}, …>` satisfies this; it does not satisfy `AuthoredComponent`.
17
+ * Named separately from `AuthoredComponent`, which is what a registry holds; a layer only needs to know
18
+ * the value is a component, and keeps the referenced component's own type for its props.
20
19
  */
21
20
  interface ComponentElement extends Authored {
22
21
  readonly kind: 'component';
@@ -582,11 +581,13 @@ type OpenTail<L extends LayerMap> = Registered extends true ? StyleProps & Modif
582
581
  */
583
582
  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, ComponentBuilder<L, P, E, D, F>> & Partial<E> & Redeclared<RootSurface<L, D, As>, P, E> & Redeclared<ForwardedPropsOf<L, F>, P, E> & AsPropsOf<As, P, E> & Redeclared<OpenTail<L>, P, E>;
584
583
  /**
585
- * `T` less the keys the component declares and the keys its render extra names — each taken out on
586
- * its own, because {@link Without} leaves `T` whole when the key set is open, and an open render
587
- * extra (`extends Record<string, unknown>`) must not stop the declared props from shadowing.
584
+ * `T` less the keys the component declares and the keys its render extra names. {@link Without} leaves
585
+ * `T` whole when its key set is open, so an open set of either kind is left out of the union rather
586
+ * than opening it: an open render extra (`extends Record<string, unknown>`) must not stop the declared
587
+ * props from shadowing. Both sets come out in one mapped type, since this runs once per component over
588
+ * every style prop and modifier the system registers.
588
589
  */
589
- type Redeclared<T, P, E> = Without<Without<T, keyof P>, Redefined<E>>;
590
+ type Redeclared<T, P, E> = string extends Redefined<E> ? Without<T, keyof P> : string extends keyof P ? Without<T, Redefined<E>> : Without<T, keyof P | Redefined<E>>;
590
591
  /**
591
592
  * The phantom call signature's parameter. `As` is inferred from `as` alone; the rest of the surface
592
593
  * is `NoInfer`, so inference never walks it. Open to inference, a spread of
@@ -642,7 +643,13 @@ type IntrinsicAttributeNames<As> = As extends keyof JSX.IntrinsicElements ? keyo
642
643
  * there is no telling a stray attribute from a style prop, and an unbuilt consumer compiles the way
643
644
  * the registry promises.
644
645
  */
645
- type ForeignAttributesOf<As, P, E> = Registered extends false ? Record<never, never> : [As] extends [keyof JSX.IntrinsicElements] ? { [K in Exclude<keyof AllHTMLAttributes<HTMLElement>, 'as' | IntrinsicAttributeNames<As> | keyof P | keyof E | keyof RegisteredStyleProps>]?: never } : Record<never, never>;
646
+ type ForeignAttributesOf<As, P, E> = Registered extends false ? Record<never, never> : [As] extends [keyof JSX.IntrinsicElements] ? { [K in Exclude<AttributeNames, IntrinsicAttributeNames<As> | keyof P | keyof E>]?: never } : Record<never, never>;
647
+ /**
648
+ * The HTML attribute names less `as` and the registered style props, which are the same for every
649
+ * component. A single `Exclude` over this concrete union takes out the rest: one over a deferred
650
+ * `Exclude<…, IntrinsicAttributeNames<As>>` would build an intersection per name while `As` is generic.
651
+ */
652
+ type AttributeNames = Exclude<keyof AllHTMLAttributes<HTMLElement>, 'as' | keyof RegisteredStyleProps>;
646
653
  /**
647
654
  * The props a component picks up from the layers it FORWARDS.
648
655
  *
@@ -753,19 +760,24 @@ type WhenAny<K, T> = [K] extends [never] ? unknown : T;
753
760
  * with `P` in full as its type argument.
754
761
  */
755
762
  type CompactProps<P extends PropMap> = [StyleRefKeys<P> | StyleRefPropKeys<P>] extends [never] ? P : { [K in keyof P as K extends StyleRefKeys<P> | StyleRefPropKeys<P> ? never : K]: P[K] } & WhenAny<StyleRefKeys<P>, StyleRefs<StyleRefKeys<P> & string>> & WhenAny<StyleRefPropKeys<P>, StyleRefProps<StyleRefPropKeys<P> & string>>;
756
- interface ComponentBuilder<L extends LayerMap, P extends PropMap,
763
+ /**
764
+ * Every type parameter is declared `in out`. Measured instead, TypeScript relates two instantiations
765
+ * with `P` generic, which crosses every registered style prop and modifier name with every other, and
766
+ * past 316 of them that is over its 100,000-member union limit (TS2590).
767
+ */
768
+ interface ComponentBuilder<in out L extends LayerMap, in out P extends PropMap,
757
769
  /** Props the RENDER takes that the config doesn't describe — React handlers, mostly (`onPress`).
758
770
  * They're part of the component's surface but not of its data, which is why they're declared at
759
771
  * `.render()` rather than in `.props()`. */
760
- E = Record<never, never>,
772
+ in out E = Record<never, never>,
761
773
  /** The defaults as AUTHORED, kept because one of them decides the type of every other prop: an
762
774
  * `as` fixes which element this renders. See {@link ElementOf}. */
763
- D = Record<never, never>,
775
+ in out D = Record<never, never>,
764
776
  /** The layers this component FORWARDS from, as a union of their names. Accumulates through
765
777
  * `.forwards()` so `ComponentPropsOf` can union each forwarded layer's element props — `root` is
766
778
  * already covered structurally by {@link ElementOf}, so for a root-only forward this changes no
767
779
  * inferred type, which is what makes replacing `inherits` a no-op at every call site. */
768
- F extends string = never, /** The states `.states()` declared — what the render knows and reports, never a caller's prop. */S extends StateMap = Record<never, never>> extends Authored {
780
+ in out F extends string = never, /** The states `.states()` declared — what the render knows and reports, never a caller's prop. */in out S extends StateMap = Record<never, never>> extends Authored {
769
781
  /**
770
782
  * The phantom call signature — the authored value is never rendered (the build substitutes the
771
783
  * emitted module), but a call site is checked against it. Generic over the element a call site
@@ -884,8 +896,19 @@ F extends string = never, /** The states `.states()` declared — what the rende
884
896
  toStored(): Record<string, unknown>;
885
897
  assignPath(path: string): void;
886
898
  }
887
- /** A component under authorship, with its declarations forgotten — what a registry holds. */
888
- type AuthoredComponent = ComponentBuilder<LayerMap, PropMap, unknown, unknown>;
899
+ /**
900
+ * A component under authorship, with its declarations forgotten — what a registry holds. It names only
901
+ * the members every builder has with the same type, so a builder of any shape is assignable to it.
902
+ */
903
+ interface AuthoredComponent extends Authored {
904
+ readonly kind: 'component';
905
+ readonly body: Record<string, unknown>;
906
+ readonly path?: string;
907
+ toStored(): Record<string, unknown>;
908
+ assignPath(path: string): void;
909
+ }
910
+ /** What the whole-body form returns: a builder whose declarations are all at their widest. */
911
+ type WidestComponentBuilder = ComponentBuilder<LayerMap, PropMap, unknown, unknown>;
889
912
  /**
890
913
  * A linked component's handle as the system that links it holds it. The build emits one for each
891
914
  * linked component the link changes, and the system's linked alias resolves to it first (UDS-4611).
@@ -931,7 +954,7 @@ interface DefineComponent {
931
954
  root: El;
932
955
  }, Record<never, never>>;
933
956
  /** The whole body at once, for a component with nothing to narrow step by step. */
934
- (body?: ComponentSourceInput<Record<string, unknown>>): AuthoredComponent;
957
+ (body?: ComponentSourceInput<Record<string, unknown>>): WidestComponentBuilder;
935
958
  layers<const L extends LayerMap>(layers: L): ComponentBuilder<L, Record<never, never>>;
936
959
  props: typeof definePropsFirst;
937
960
  }
@@ -402,24 +402,6 @@ function toStored(body) {
402
402
  styles: storedStyleRules(rules)
403
403
  };
404
404
  }
405
- /**
406
- * Author a component.
407
- *
408
- * Callable for the case with nothing to narrow — `defineComponent({ layers: { root: 'div' } })` — and
409
- * chainable for every other, since a chain is what lets each step's types come from the last.
410
- */
411
- /**
412
- * Author a component.
413
- *
414
- * Called with an ELEMENT — `defineComponent('input')`, `defineComponent(Component.ref('Box'))` — that
415
- * element becomes the root layer, because a component with one layer IS that element and saying so
416
- * twice adds nothing. It's also what makes the element's props type correctly: v1 expressed this as a
417
- * `defaultProps: { as: 'input' }` on a `div`-rooted component, which left the config claiming the
418
- * element was a `div` while every call site treated it as an input.
419
- *
420
- * The render follows from it too — one layer needs no anatomy, so the build generates the element
421
- * rather than reading a `.render()` that would only restate the layer.
422
- */
423
405
  function defineComponentImpl(bodyOrElement) {
424
406
  if (isComponentElement(bodyOrElement)) return build({ layers: { root: bodyOrElement } });
425
407
  if (typeof bodyOrElement === "function" || isRef(bodyOrElement) && kindOf(bodyOrElement) === "package") throw new Error("[uds] defineComponent(<package export>): a package export cannot be a component's only layer yet — the generated one-layer render emits web elements. Declare `layers` and a `.render()` instead.");
@@ -11,7 +11,8 @@
11
11
  * them.
12
12
  *
13
13
  * Two ways in. {@link snapshotHtml} reads the markup `react-dom/server` produces, which is what
14
- * `uds anatomy` compares in a Bun process — structure, text and classes, since no layout runs there.
14
+ * `uds anatomy` compares in a Bun process — structure, text, classes, inline style and a plain tag's
15
+ * attributes, since no layout runs there.
15
16
  * A browser surface snapshots live DOM instead (the studio's `?anatomyCheck=true`), and adds computed
16
17
  * style to each node; {@link diffRendered} compares whatever a node carries.
17
18
  */
@@ -34,10 +35,17 @@ interface RenderedNode {
34
35
  readonly style: Readonly<Record<string, string>>;
35
36
  /** `true` when `style` holds inline declarations rather than a computed subset. */
36
37
  readonly inlineStyle?: boolean;
38
+ /**
39
+ * A plain tag's attributes — an element that is no component's layer, such as an svg `<path>`,
40
+ * whose attributes are often its paint (`fill-opacity`, `stroke-width`, `d`). Left out for a layer,
41
+ * whose attributes come from its component, and where the snapshot can't read them. See
42
+ * {@link paintAttributesOf} for what is skipped.
43
+ */
44
+ readonly attributes?: Readonly<Record<string, string>>;
37
45
  /** What the alignment matches on — see {@link renderedSignatureOf}. */
38
46
  readonly signature: string;
39
47
  }
40
- type AnatomyIssueKind = /** The module renders this element and the anatomy does not. */'missing' /** The anatomy renders this element and the module does not. */ | 'extra' /** Both render it — aligned as the same layer — as different elements (`div` against `span`). */ | 'tag' /** Both render it, with different own text. */ | 'text' /** Both render it, with a computed style that differs beyond tolerance. */ | 'style'
48
+ type AnatomyIssueKind = /** The module renders this element and the anatomy does not. */'missing' /** The anatomy renders this element and the module does not. */ | 'extra' /** Both render it — aligned as the same layer — as different elements (`div` against `span`). */ | 'tag' /** Both render it, with different own text. */ | 'text' /** Both render it as a plain tag, with an attribute that differs or that one side lacks. */ | 'attribute' /** Both render it, with a computed style that differs beyond tolerance. */ | 'style'
41
49
  /** Both render it, with different class sets. Classes are how a style arrives, so where a
42
50
  * computed style is available this is informational; where none is (markup only) it is the
43
51
  * paint difference itself — see `diffRendered`'s `classesFail`. */
@@ -90,7 +98,7 @@ interface DiffOptions {
90
98
  *
91
99
  * Elements align by signature in document order; what the module renders and the anatomy does not
92
100
  * is `missing`, the reverse is `extra`. Aligned elements then compare own text, computed style (when
93
- * both sides carry it) and class set.
101
+ * both sides carry it), a plain tag's attributes (when both sides carry them) and class set.
94
102
  */
95
103
  declare function diffRendered(anatomy: readonly RenderedNode[], instance: readonly RenderedNode[], options?: DiffOptions): AnatomyReport;
96
104
  /** The allowlist spelling of an issue: kind, node, and the property for a style difference. */
@@ -161,6 +161,31 @@ function ownTextOf(element) {
161
161
  for (const child of element.children) if ("text" in child) text += child.text;
162
162
  return text.replace(/\s+/g, " ").trim();
163
163
  }
164
+ /** Attributes that name an id rather than paint: React mints ids per render, so the two sides differ. */
165
+ const ID_ATTRIBUTES = new Set([
166
+ "id",
167
+ "for",
168
+ "aria-activedescendant",
169
+ "aria-controls",
170
+ "aria-describedby",
171
+ "aria-labelledby",
172
+ "aria-owns"
173
+ ]);
174
+ /**
175
+ * The attributes of a plain tag that the two renderings should agree on: every one but `class` and
176
+ * `style` (compared on their own), `data-*` (markers), and what names an id — an id attribute, or a
177
+ * value pointing at one (`url(#…)`, `#…`).
178
+ */
179
+ function paintAttributesOf(attributes) {
180
+ const out = {};
181
+ for (const [name, value] of Object.entries(attributes)) {
182
+ if (name === "class" || name === "style" || name.startsWith("data-")) continue;
183
+ if (ID_ATTRIBUTES.has(name)) continue;
184
+ if (value.includes("url(#") || value.startsWith("#")) continue;
185
+ out[name] = value;
186
+ }
187
+ return out;
188
+ }
164
189
  function firstComponentRoot(node) {
165
190
  for (const child of node.children) {
166
191
  if ("text" in child) continue;
@@ -190,6 +215,7 @@ function snapshotHtml(html) {
190
215
  classes: (element.attributes.class ?? "").split(/\s+/).filter(Boolean).sort(),
191
216
  style: inlineStyleOf(element.attributes.style),
192
217
  inlineStyle: true,
218
+ ...component === void 0 ? { attributes: paintAttributesOf(element.attributes) } : {},
193
219
  signature: renderedSignatureOf(element.tag, component, layer)
194
220
  });
195
221
  for (const child of element.children) if (!("text" in child)) visit(child);
@@ -255,7 +281,7 @@ function nodeLabel(node, ordinal) {
255
281
  *
256
282
  * Elements align by signature in document order; what the module renders and the anatomy does not
257
283
  * is `missing`, the reverse is `extra`. Aligned elements then compare own text, computed style (when
258
- * both sides carry it) and class set.
284
+ * both sides carry it), a plain tag's attributes (when both sides carry them) and class set.
259
285
  */
260
286
  function diffRendered(anatomy, instance, options = {}) {
261
287
  const issues = [];
@@ -310,6 +336,21 @@ function diffRendered(anatomy, instance, options = {}) {
310
336
  instance: vb === void 0 ? void 0 : normalizeStyleValue(vb)
311
337
  });
312
338
  }
339
+ if (a.attributes && b.attributes) {
340
+ const names = [...new Set([...Object.keys(a.attributes), ...Object.keys(b.attributes)])].sort();
341
+ for (const name of names) {
342
+ const va = a.attributes[name];
343
+ const vb = b.attributes[name];
344
+ if (va === vb) continue;
345
+ issues.push({
346
+ kind: "attribute",
347
+ node: label,
348
+ property: name,
349
+ anatomy: va,
350
+ instance: vb
351
+ });
352
+ }
353
+ }
313
354
  const onlyA = a.classes.filter((c) => !b.classes.includes(c));
314
355
  const onlyB = b.classes.filter((c) => !a.classes.includes(c));
315
356
  if (onlyA.length || onlyB.length) issues.push({
@@ -975,6 +975,7 @@ function slotTargets(config, path, body) {
975
975
  }
976
976
  function computeSlotTargets(config, body) {
977
977
  const byLayer = /* @__PURE__ */ new Map();
978
+ const targeted = /* @__PURE__ */ new Map();
978
979
  const untargeted = [];
979
980
  for (const [from, decl] of Object.entries(body.props ?? {})) {
980
981
  if (decl?.type === "slot") {
@@ -988,11 +989,15 @@ function computeSlotTargets(config, body) {
988
989
  if (decl?.type !== "forward") continue;
989
990
  if (!routesContent(config, body.layers, decl)) continue;
990
991
  const target = refOf(decl.target);
991
- if (target) byLayer.set(pathOf(target), {
992
- ...decl,
993
- from
994
- });
995
- else untargeted.push({
992
+ if (target) {
993
+ const layer = pathOf(target);
994
+ const route = {
995
+ ...decl,
996
+ from
997
+ };
998
+ byLayer.set(layer, route);
999
+ targeted.set(layer, [...(targeted.get(layer) ?? []).filter((other) => intoName(other.into) !== intoName(route.into)), route]);
1000
+ } else untargeted.push({
996
1001
  ...decl,
997
1002
  from,
998
1003
  untargeted: true
@@ -1003,6 +1008,7 @@ function computeSlotTargets(config, body) {
1003
1008
  if (root && content && !byLayer.has(root)) byLayer.set(root, content);
1004
1009
  return {
1005
1010
  byLayer,
1011
+ targeted,
1006
1012
  untargeted
1007
1013
  };
1008
1014
  }
@@ -1024,9 +1030,9 @@ function computeSlotTargets(config, body) {
1024
1030
  function slotTargetsOf(config, path) {
1025
1031
  const body = bodyOf(config, path);
1026
1032
  if (!body) return [];
1027
- const { byLayer, untargeted } = slotTargets(config, path, body);
1033
+ const { targeted: targetedByLayer, untargeted } = slotTargets(config, path, body);
1028
1034
  const root = rootKeyOf(body.layers ?? {});
1029
- return [...[...byLayer].filter(([, decl]) => !decl.untargeted).map(([layer, decl]) => ({
1035
+ return [...[...targetedByLayer].flatMap(([layer, routes]) => routes.map((decl) => [layer, decl])).map(([layer, decl]) => ({
1030
1036
  layer,
1031
1037
  from: decl.from,
1032
1038
  into: intoName(decl.into)
@@ -1288,7 +1294,7 @@ function expandInstance(sink, path, instanceProps, content, hint, instanceSlots)
1288
1294
  ...Object.fromEntries(Object.entries(pins).map(([key, value]) => key.startsWith("_") ? [key, value === true || value === "true"] : [key, value]))
1289
1295
  };
1290
1296
  const consumed = consumedProps(config, path, body);
1291
- const { byLayer: targets } = slotTargets(config, path, body);
1297
+ const { byLayer: targets, targeted: targetedRoutes } = slotTargets(config, path, body);
1292
1298
  const valueRoutes = /* @__PURE__ */ new Map();
1293
1299
  for (const route of valueForwards(config, body)) {
1294
1300
  const routes = valueRoutes.get(route.layer) ?? [];
@@ -1331,6 +1337,7 @@ function expandInstance(sink, path, instanceProps, content, hint, instanceSlots)
1331
1337
  let contentPlaced = false;
1332
1338
  const routedFroms = /* @__PURE__ */ new Set();
1333
1339
  for (const target of targets.values()) routedFroms.add(target.from);
1340
+ for (const routes of targetedRoutes.values()) for (const route of routes) routedFroms.add(route.from);
1334
1341
  const positioned = /* @__PURE__ */ new Set();
1335
1342
  for (const node of Object.values(anatomy)) {
1336
1343
  const prop = slotAnatomyPropOf(node);
@@ -1428,6 +1435,8 @@ function expandInstance(sink, path, instanceProps, content, hint, instanceSlots)
1428
1435
  }
1429
1436
  const slot = layer ? targets.get(layer) : void 0;
1430
1437
  const slotValue = slot && !subtreeBinds(anatomy, key, slot.from) && !(positioned.has(slot.from) && isText(instanceProps[slot.from])) ? instanceProps[slot.from] : void 0;
1438
+ const siblingRoutes = layer ? (targetedRoutes.get(layer) ?? []).filter((route) => route !== slot) : [];
1439
+ const siblingValues = Object.fromEntries(siblingRoutes.filter((route) => instanceProps[route.from] !== void 0 && !subtreeBinds(anatomy, key, route.from)).map((route) => [intoName(route.into), instanceProps[route.from]]));
1431
1440
  const props = layer ? {
1432
1441
  ...withoutKeys(authoredProps(config, node.props), ruleKeysFor(config, path, layer, state, pins)),
1433
1442
  "data-uds-component": path,
@@ -1438,6 +1447,7 @@ function expandInstance(sink, path, instanceProps, content, hint, instanceSlots)
1438
1447
  ...isRoot ? componentStateAttributes(componentStates(config, path), state) : {},
1439
1448
  ...isRoot || type === void 0 || kindOf(type) !== "component" ? forcedModifierAttributes(pins, path) : {},
1440
1449
  ...isRoot ? rest : {},
1450
+ ...siblingValues,
1441
1451
  ...slot && slotValue !== void 0 ? { [intoName(slot.into)]: slotValue } : {},
1442
1452
  ...layerProps[layer],
1443
1453
  className: classesFor(config, path, layer, state, [
@@ -1513,6 +1523,10 @@ function expandInstance(sink, path, instanceProps, content, hint, instanceSlots)
1513
1523
  if (placedFills.length) slots[name] = placedFills;
1514
1524
  }
1515
1525
  if (overridden && routed) slots[overridden] = [...routed];
1526
+ for (const route of siblingRoutes) {
1527
+ const fills = instanceSlots?.[route.from];
1528
+ if (fills?.length) slots[intoName(route.into)] = [...fills];
1529
+ }
1516
1530
  if (routed?.length && !overridden && !(slot && positioned.has(slot.from))) children.push(...routed);
1517
1531
  const placed = place(nested, type, props, children, keyHint, Object.keys(slots).length ? slots : void 0);
1518
1532
  if (visible === void 0) withCondition(nested, placed, { visible: node.visible });
@@ -1,5 +1,6 @@
1
1
  import { FragmentRenderer } from "./primitives/FragmentRenderer.js";
2
2
  import { SlotRenderer } from "./primitives/SlotRenderer.js";
3
+ import { glyphMemberFor } from "../spec/glyph-members.js";
3
4
  import { wrapComponentRefs } from "./wrappers/component-refs.js";
4
5
  import { wrapEventBridge } from "./wrappers/event-bridge.js";
5
6
  import { wrapGlyphProps } from "./wrappers/glyph-props.js";
@@ -28,7 +29,10 @@ function wrapRegistry(reg, descriptor, iconMembers) {
28
29
  };
29
30
  const stylePropNames = descriptor?.stylePropClasses ? new Set([...Object.keys(descriptor.stylePropClasses), ...Object.values(descriptor.cssPropertyToStyleProp ?? {})]) : void 0;
30
31
  const holder = {};
31
- const final = aliasHtmlTypes(wrapInlineStyleProps(wrapNormalizeHex(wrapEventBridge(wrapGlyphProps(wrapComponentRefs(base, (type) => holder.registry?.[type]), descriptor?.glyphProps, iconMembers)), stylePropNames), {
32
+ const final = aliasHtmlTypes(wrapInlineStyleProps(wrapNormalizeHex(wrapEventBridge(wrapGlyphProps(wrapComponentRefs(base, (type) => holder.registry?.[type], iconMembers ? (ref) => glyphMemberFor({
33
+ ref,
34
+ members: iconMembers
35
+ }) : void 0), descriptor?.glyphProps, iconMembers)), stylePropNames), {
32
36
  cssPropertyToStyleProp: descriptor?.cssPropertyToStyleProp,
33
37
  layoutComponents: descriptor?.layoutComponents,
34
38
  textComponents: descriptor?.textComponents
@@ -1,4 +1,5 @@
1
1
  import type { ComponentRegistry, ComponentRenderer } from '@json-render/react';
2
+ import { type PreviewRef } from '../../spec/preview-elements';
2
3
  /**
3
4
  * A component handed as a prop VALUE (`icon={DotsThreeIcon}`) crosses the config as a bare ref,
4
5
  * `{ __ref: 'icon:phosphor/DotsThreeIcon' }`, keyed the way the registry keys the component. This
@@ -11,4 +12,6 @@ import type { ComponentRegistry, ComponentRenderer } from '@json-render/react';
11
12
  * like any spec element. One component per ref, so the receiving component's element type is stable
12
13
  * across renders and React keeps the glyph mounted.
13
14
  */
14
- export declare function wrapComponentRefs(reg: ComponentRegistry, resolve: (type: string) => ComponentRenderer | undefined): ComponentRegistry;
15
+ export declare function wrapComponentRefs(reg: ComponentRegistry, resolve: (type: string) => ComponentRenderer | undefined,
16
+ /** The member an `icon:` ref names, for a glyph nested inside a prop's data. */
17
+ glyphOf?: (ref: PreviewRef) => unknown): ComponentRegistry;
@@ -20,14 +20,24 @@ function hasRefDeep(value) {
20
20
  if (isComponentRef(value)) return true;
21
21
  return (Array.isArray(value) ? value : Object.values(value)).some(hasRefDeep);
22
22
  }
23
- function reviveDeep(value, revive) {
23
+ function reviveDeep({ value, revive, glyphOf, nested = false }) {
24
24
  if (typeof value !== "object" || value === null) return value;
25
25
  if ("$$typeof" in value) return value;
26
26
  if (isPreviewElement(value)) return value;
27
- if (isComponentRef(value)) return revive(value);
28
- if (Array.isArray(value)) return value.map((item) => reviveDeep(item, revive));
27
+ if (isComponentRef(value)) return (nested ? glyphOf?.(value) : void 0) ?? revive(value);
28
+ if (Array.isArray(value)) return value.map((item) => reviveDeep({
29
+ value: item,
30
+ revive,
31
+ glyphOf,
32
+ nested: true
33
+ }));
29
34
  const out = {};
30
- for (const [key, member] of Object.entries(value)) out[key] = reviveDeep(member, revive);
35
+ for (const [key, member] of Object.entries(value)) out[key] = reviveDeep({
36
+ value: member,
37
+ revive,
38
+ glyphOf,
39
+ nested: true
40
+ });
31
41
  return out;
32
42
  }
33
43
  /**
@@ -42,7 +52,7 @@ function reviveDeep(value, revive) {
42
52
  * like any spec element. One component per ref, so the receiving component's element type is stable
43
53
  * across renders and React keeps the glyph mounted.
44
54
  */
45
- function wrapComponentRefs(reg, resolve) {
55
+ function wrapComponentRefs(reg, resolve, glyphOf) {
46
56
  const revived = /* @__PURE__ */ new Map();
47
57
  const revive = (ref) => {
48
58
  const type = ref.__ref;
@@ -72,7 +82,11 @@ function wrapComponentRefs(reg, resolve) {
72
82
  ...renderProps,
73
83
  element: {
74
84
  ...renderProps.element,
75
- props: reviveDeep(props, revive)
85
+ props: Object.fromEntries(Object.entries(props).map(([name, value]) => [name, reviveDeep({
86
+ value,
87
+ revive,
88
+ glyphOf
89
+ })]))
76
90
  }
77
91
  });
78
92
  };
@@ -5,6 +5,10 @@ import { ComponentRegistry } from "@json-render/react";
5
5
  * Set to `true` on an intrinsic element whose props are the tag's own attributes. A style-prop key
6
6
  * whose value names no class is then kept as the attribute rather than dropped, so a captured
7
7
  * `<svg width={16}>` keeps its `width`. The key never reaches the DOM.
8
+ *
9
+ * An element a component's render placed inline (the anatomy marker, `data-uds-anatomy`) is one
10
+ * without the flag: its props are the JSX attributes the module renders as attributes, so a
11
+ * `<path fillOpacity={0}>` keeps its `fill-opacity` as the module does.
8
12
  */
9
13
  declare const VERBATIM_ATTRIBUTES = "__verbatimAttributes";
10
14
  /**
@@ -1,3 +1,4 @@
1
+ import "../../framework/render-spec.js";
1
2
  import { specContent } from "../spec-content.js";
2
3
  import React from "react";
3
4
  //#region src/renderer/wrappers/html-aliases.tsx
@@ -16,6 +17,10 @@ const FORBIDDEN_INTRINSIC_PROPS = new Set(["dangerouslySetInnerHTML"]);
16
17
  * Set to `true` on an intrinsic element whose props are the tag's own attributes. A style-prop key
17
18
  * whose value names no class is then kept as the attribute rather than dropped, so a captured
18
19
  * `<svg width={16}>` keeps its `width`. The key never reaches the DOM.
20
+ *
21
+ * An element a component's render placed inline (the anatomy marker, `data-uds-anatomy`) is one
22
+ * without the flag: its props are the JSX attributes the module renders as attributes, so a
23
+ * `<path fillOpacity={0}>` keeps its `fill-opacity` as the module does.
19
24
  */
20
25
  const VERBATIM_ATTRIBUTES = "__verbatimAttributes";
21
26
  const NullEl = () => null;
@@ -43,7 +48,7 @@ function aliasHtmlTypes(reg, stylePropClasses) {
43
48
  if (typeof prop === "string" && INTRINSIC_TAG_RE.test(prop)) {
44
49
  const IntrinsicEl = ({ element, children, slots }) => {
45
50
  const props = element.props;
46
- const verbatim = props?.[VERBATIM_ATTRIBUTES] === true;
51
+ const verbatim = props?.["__verbatimAttributes"] === true || typeof props?.["data-uds-anatomy"] === "string";
47
52
  const intrinsicProps = {};
48
53
  const styleClasses = [];
49
54
  for (const [key, value] of Object.entries(props ?? {})) {