@yahoo/uds-create-config 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/dist/AssetGroup.d.ts +77 -0
  2. package/dist/AssetGroup.js +125 -0
  3. package/dist/Component.d.ts +308 -0
  4. package/dist/Component.js +896 -0
  5. package/dist/ComponentGroup.d.ts +20 -0
  6. package/dist/ComponentGroup.js +46 -0
  7. package/dist/CompositeStyle.d.ts +27 -0
  8. package/dist/CompositeStyle.js +52 -0
  9. package/dist/Config.d.ts +423 -0
  10. package/dist/Config.js +1429 -0
  11. package/dist/Mode.d.ts +41 -0
  12. package/dist/Mode.js +81 -0
  13. package/dist/Modifier.d.ts +51 -0
  14. package/dist/Modifier.js +97 -0
  15. package/dist/MotionDef.d.ts +49 -0
  16. package/dist/MotionDef.js +97 -0
  17. package/dist/Props.d.ts +317 -0
  18. package/dist/Props.js +35 -0
  19. package/dist/Provider.d.ts +20 -0
  20. package/dist/Provider.js +14 -0
  21. package/dist/StyleProp.d.ts +112 -0
  22. package/dist/StyleProp.js +197 -0
  23. package/dist/Token.d.ts +55 -0
  24. package/dist/Token.js +112 -0
  25. package/dist/TokenGroup.d.ts +32 -0
  26. package/dist/TokenGroup.js +68 -0
  27. package/dist/asset-kind.d.ts +55 -0
  28. package/dist/asset-kind.js +29 -0
  29. package/dist/brands.d.ts +30 -0
  30. package/dist/brands.js +20 -0
  31. package/dist/captureCallerPath.d.ts +48 -0
  32. package/dist/captureCallerPath.js +95 -0
  33. package/dist/colorExpressions.d.ts +131 -0
  34. package/dist/colorExpressions.js +148 -0
  35. package/dist/defineAssetGroup.d.ts +166 -0
  36. package/dist/defineAssetGroup.js +264 -0
  37. package/dist/defineProvider.d.ts +29 -0
  38. package/dist/defineProvider.js +60 -0
  39. package/dist/element-marker.d.ts +54 -0
  40. package/dist/element-marker.js +113 -0
  41. package/dist/entity-utils.d.ts +56 -0
  42. package/dist/entity-utils.js +105 -0
  43. package/dist/factories.d.ts +707 -0
  44. package/dist/factories.js +393 -0
  45. package/dist/foreign-component-name.d.ts +21 -0
  46. package/dist/foreign-component-name.js +42 -0
  47. package/dist/index.d.ts +32 -0
  48. package/dist/index.js +28 -0
  49. package/dist/interpolate.d.ts +20 -0
  50. package/dist/interpolate.js +10 -0
  51. package/dist/jsx/__fixtures__/cross-component-preview.d.ts +3 -0
  52. package/dist/jsx/__fixtures__/cross-component-preview.js +15 -0
  53. package/dist/jsx/jsx-dev-runtime.d.ts +15 -0
  54. package/dist/jsx/jsx-dev-runtime.js +11 -0
  55. package/dist/jsx/jsx-runtime.d.ts +48 -0
  56. package/dist/jsx/jsx-runtime.js +305 -0
  57. package/dist/markers.d.ts +217 -0
  58. package/dist/markers.js +67 -0
  59. package/dist/refs.d.ts +158 -0
  60. package/dist/refs.js +104 -0
  61. package/dist/renderer/RendererErrorBoundary.d.ts +26 -0
  62. package/dist/renderer/RendererErrorBoundary.js +30 -0
  63. package/dist/renderer/UdsRenderer.d.ts +80 -0
  64. package/dist/renderer/UdsRenderer.js +33 -0
  65. package/dist/renderer/assetRenderable.d.ts +13 -0
  66. package/dist/renderer/assetRenderable.js +13 -0
  67. package/dist/renderer/index.d.ts +14 -0
  68. package/dist/renderer/index.js +14 -0
  69. package/dist/renderer/makeRegistry.d.ts +34 -0
  70. package/dist/renderer/makeRegistry.js +52 -0
  71. package/dist/renderer/makeUdsRenderer.d.ts +29 -0
  72. package/dist/renderer/makeUdsRenderer.js +35 -0
  73. package/dist/renderer/primitives/FragmentRenderer.d.ts +12 -0
  74. package/dist/renderer/primitives/FragmentRenderer.js +11 -0
  75. package/dist/renderer/primitives/SlotRenderer.d.ts +21 -0
  76. package/dist/renderer/primitives/SlotRenderer.js +20 -0
  77. package/dist/renderer/wrapRegistry.d.ts +64 -0
  78. package/dist/renderer/wrapRegistry.js +39 -0
  79. package/dist/renderer/wrappers/component-slots.d.ts +41 -0
  80. package/dist/renderer/wrappers/component-slots.js +66 -0
  81. package/dist/renderer/wrappers/event-bridge.d.ts +20 -0
  82. package/dist/renderer/wrappers/event-bridge.js +78 -0
  83. package/dist/renderer/wrappers/hex-normalize.d.ts +14 -0
  84. package/dist/renderer/wrappers/hex-normalize.js +35 -0
  85. package/dist/renderer/wrappers/html-aliases.d.ts +24 -0
  86. package/dist/renderer/wrappers/html-aliases.js +62 -0
  87. package/dist/renderer/wrappers/inline-styles.d.ts +16 -0
  88. package/dist/renderer/wrappers/inline-styles.js +104 -0
  89. package/dist/renderer/wrappers/slot-resolution.d.ts +25 -0
  90. package/dist/renderer/wrappers/slot-resolution.js +68 -0
  91. package/dist/renderer/wrappers/void-elements.d.ts +23 -0
  92. package/dist/renderer/wrappers/void-elements.js +28 -0
  93. package/dist/spec/apply-forced-modifiers.d.ts +19 -0
  94. package/dist/spec/apply-forced-modifiers.js +49 -0
  95. package/dist/spec/asset-jsx.d.ts +48 -0
  96. package/dist/spec/asset-jsx.js +48 -0
  97. package/dist/spec/collapse-text-labels.d.ts +44 -0
  98. package/dist/spec/collapse-text-labels.js +107 -0
  99. package/dist/spec/empty-node-slots.d.ts +51 -0
  100. package/dist/spec/empty-node-slots.js +141 -0
  101. package/dist/spec/index.d.ts +10 -0
  102. package/dist/spec/index.js +10 -0
  103. package/dist/spec/jsxToSpec.d.ts +48 -0
  104. package/dist/spec/jsxToSpec.js +506 -0
  105. package/dist/spec/layer-props.d.ts +52 -0
  106. package/dist/spec/layer-props.js +149 -0
  107. package/dist/spec/preview-controls.d.ts +44 -0
  108. package/dist/spec/preview-controls.js +139 -0
  109. package/dist/spec/slot-refs.d.ts +39 -0
  110. package/dist/spec/slot-refs.js +56 -0
  111. package/dist/spec/specToJsx.d.ts +35 -0
  112. package/dist/spec/specToJsx.js +127 -0
  113. package/dist/token-override-rows.d.ts +66 -0
  114. package/dist/token-override-rows.js +223 -0
  115. package/dist/tokenValueType.d.ts +34 -0
  116. package/dist/tokenValueType.js +138 -0
  117. package/dist/tsconfig.tsbuildinfo +1 -0
  118. package/dist/types/css-properties.d.ts +232 -0
  119. package/dist/types/css-properties.js +14 -0
  120. package/dist/types/css-property-keywords.d.ts +156 -0
  121. package/dist/types/css-property-keywords.js +616 -0
  122. package/dist/types/css-values.d.ts +63 -0
  123. package/dist/types/css-values.js +16 -0
  124. package/dist/types.d.ts +708 -0
  125. package/dist/types.js +12 -0
  126. package/dist/units.d.ts +14 -0
  127. package/dist/units.js +16 -0
  128. package/dist/utils/index.d.ts +4 -0
  129. package/dist/utils/index.js +4 -0
  130. package/package.json +81 -0
@@ -0,0 +1,149 @@
1
+ import { makeTokenRefToCss } from "../entity-utils.js";
2
+ import { isTokenRef } from "../refs.js";
3
+ import { isColorExpression, resolveColorExpression } from "../colorExpressions.js";
4
+ //#region src/spec/layer-props.ts
5
+ const REST_STATE = "rest";
6
+ const isStateKey = (key) => key.startsWith("_");
7
+ /**
8
+ * Resolve the styles a layer effectively has for a set of prop-axis values and
9
+ * (optionally) one forced interaction state, walking the authoring surfaces
10
+ * codegen reads:
11
+ *
12
+ * 1. `component.base[layer]` — the always-on baseline.
13
+ * 2. `component.props[axis].values[value][layer]` — per axis key in `propValues`.
14
+ * 3. `component.compoundProps[].layers[layer]` — for entries whose `when`
15
+ * is a subset of `propValues` (multi-axis overrides).
16
+ * 4. State overlay — when `state` names a modifier sub-object (`_hover`) on
17
+ * the merged surface, its props overlay the base so a static preview
18
+ * shows that state.
19
+ *
20
+ * `propValues` are plain axis names → values (no reserved keys). `state` is a
21
+ * single forced interaction modifier (`'_hover'`); pass it directly rather than
22
+ * smuggling it into `propValues`. **Modes are intentionally not a parameter** —
23
+ * mode visuals ride a scoped wrapper class (`.dark &`) and flip live, so they
24
+ * are never folded into the static base here.
25
+ */
26
+ function resolveAuthoredLayerProps(component, layer, propValues, state) {
27
+ if (!component) return {};
28
+ const merged = { ...component.base?.[layer] ?? {} };
29
+ for (const [key, value] of Object.entries(propValues)) {
30
+ const binding = component.props?.[key];
31
+ if (!binding || typeof binding !== "object") continue;
32
+ const layerStyles = pickAxisLayers(binding, value)?.[layer];
33
+ if (layerStyles) mergeLayerStyles(merged, layerStyles);
34
+ }
35
+ if (component.compoundProps) for (const entry of component.compoundProps) {
36
+ if (!matchesContext(entry.when, propValues)) continue;
37
+ const layerStyles = entry.layers[layer];
38
+ if (layerStyles && typeof layerStyles === "object") mergeLayerStyles(merged, layerStyles);
39
+ }
40
+ if (state && state !== REST_STATE && isStateKey(state)) {
41
+ const stateStyles = merged[state];
42
+ if (stateStyles && typeof stateStyles === "object") Object.assign(merged, stateStyles);
43
+ }
44
+ return merged;
45
+ }
46
+ /**
47
+ * Resolve per-layer style bags for a set of prop values, mirroring the config's
48
+ * variant resolution. `buildRenderSpec` calls this once per variant combo to
49
+ * bake the bags onto the render spec.
50
+ *
51
+ * `state` is left `null` by the bake path — modifier blocks ride through as
52
+ * nested style props and forced state is applied per cell (the render-spec
53
+ * consumers fold the active modifier into props), so folding it into the base
54
+ * here would be redundant. (Derived callers like the layers panel also pass no
55
+ * state.) `cssVarPrefix` resolves color expressions to a `var(...)`-referencing
56
+ * arbitrary value.
57
+ */
58
+ function resolveRenderedLayerProps(component, propValues, state, cssVarPrefix) {
59
+ if (!component) return {};
60
+ const layerStyles = {};
61
+ for (const layerName of component.layers.keys()) layerStyles[layerName] = finalizeLayerProps(resolveAuthoredLayerProps(component, layerName, propValues, state), state, cssVarPrefix);
62
+ return layerStyles;
63
+ }
64
+ /**
65
+ * Fold the active (selected) state's styles into the flat bag so the static
66
+ * preview shows that state without real interaction, KEEP modifier blocks as
67
+ * nested objects so the component renderer emits their classes (`_hover:bg-x`,
68
+ * `_stackedChildren:ms-[-6px]`) — structural modifiers (child combinators,
69
+ * attrs) apply and interaction states stay live — and bake color expressions
70
+ * into arbitrary style-prop values so they flow through the class system
71
+ * instead of an inline `style`.
72
+ */
73
+ function finalizeLayerProps(styles, state, cssVarPrefix) {
74
+ const merged = { ...styles };
75
+ const stateStyles = state && isStateKey(state) ? styles[state] : void 0;
76
+ if (stateStyles && typeof stateStyles === "object") Object.assign(merged, stateStyles);
77
+ return bakeColorValues(merged, makeTokenRefToCss(cssVarPrefix));
78
+ }
79
+ /**
80
+ * Resolve every color expression / token ref in a layer's style map to an
81
+ * arbitrary `[…]` value, recursing into nested modifier blocks. A
82
+ * `color-mix(...)` / `var(--…)` isn't a pre-compiled token class, so the
83
+ * bracket form lets `getClassName` encode it into a class the same way it
84
+ * handles `[16px]` — keeping styling renderer-agnostic (no inline `style` whose
85
+ * `var(...)` the build would tree-shake).
86
+ */
87
+ function bakeColorValues(styles, tokenRefToCss) {
88
+ const out = {};
89
+ for (const [key, value] of Object.entries(styles)) if (isColorExpression(value)) out[key] = `[${resolveColorExpression(value, tokenRefToCss)}]`;
90
+ else if (isTokenRef(value)) out[key] = `[${tokenRefToCss(value.ref)}]`;
91
+ else if (value !== null && typeof value === "object" && !Array.isArray(value)) out[key] = bakeColorValues(value, tokenRefToCss);
92
+ else out[key] = value;
93
+ return out;
94
+ }
95
+ /**
96
+ * Layer `from` onto `into`, deep-merging interaction-state modifier blocks
97
+ * (`_hover`, `_disabled`, …) instead of replacing them. A variant's
98
+ * `_disabled: { borderColor }` must combine with `base`'s `_disabled: { opacity }`
99
+ * — a shallow `Object.assign` would drop the base opacity, so the
100
+ * secondary+disabled cell loses its dimming. Non-modifier props still
101
+ * shallow-replace (a later authoring surface wins that property outright).
102
+ */
103
+ function mergeLayerStyles(into, from) {
104
+ for (const [key, value] of Object.entries(from)) {
105
+ const existing = into[key];
106
+ if (isStateKey(key) && isStyleMap(existing) && isStyleMap(value)) into[key] = {
107
+ ...existing,
108
+ ...value
109
+ };
110
+ else into[key] = value;
111
+ }
112
+ }
113
+ /**
114
+ * Pull the per-layer style map keyed by `value` off a `variant` / `boolean`
115
+ * marker. Booleans accept a real boolean or its string form.
116
+ */
117
+ function pickAxisLayers(binding, value) {
118
+ if (binding.__kind === "variant") {
119
+ const values = binding.values;
120
+ if (!values) return void 0;
121
+ const entry = values[String(value)];
122
+ return isStyleMap(entry) ? entry : void 0;
123
+ }
124
+ if (binding.__kind === "boolean") {
125
+ const values = binding.values;
126
+ if (!values) return void 0;
127
+ const key = coerceBooleanKey(value);
128
+ if (key === void 0) return void 0;
129
+ const entry = values[key];
130
+ return isStyleMap(entry) ? entry : void 0;
131
+ }
132
+ }
133
+ function isStyleMap(value) {
134
+ return typeof value === "object" && value !== null && !Array.isArray(value);
135
+ }
136
+ function coerceBooleanKey(value) {
137
+ if (value === true || value === "true") return "true";
138
+ if (value === false || value === "false") return "false";
139
+ }
140
+ /**
141
+ * `when` is a subset of `context` when every key in `when` is present in
142
+ * `context` with the same stringified value.
143
+ */
144
+ function matchesContext(when, context) {
145
+ for (const [k, v] of Object.entries(when)) if (String(context[k]) !== String(v)) return false;
146
+ return true;
147
+ }
148
+ //#endregion
149
+ export { resolveAuthoredLayerProps, resolveRenderedLayerProps };
@@ -0,0 +1,44 @@
1
+ import { Component } from "../Component.js";
2
+ import { Config } from "../Config.js";
3
+
4
+ //#region src/spec/preview-controls.d.ts
5
+ type PreviewControlKind = 'variant' | 'boolean' | 'string';
6
+ interface PreviewControl {
7
+ readonly name: string;
8
+ readonly kind: PreviewControlKind;
9
+ readonly defaultValue: unknown;
10
+ /** Selectable values — set for `variant` controls (incl. composite/styleProp). */
11
+ readonly options?: readonly string[];
12
+ }
13
+ /**
14
+ * The selectable values for a prop, or `undefined` when it isn't option-backed.
15
+ * Resolves by the prop's declared kind: a `variant()`'s declared values, a
16
+ * `composite()`'s registered style keys (e.g. `textVariant` → its variant
17
+ * names), or a `styleProp()`'s token + keyword names. A name that isn't a
18
+ * declared prop falls back to a same-named registered style prop (the Box
19
+ * surface — e.g. `color`/`bg` set only via `preview.defaultProps`).
20
+ */
21
+ declare function propOptions(config: Config, component: Component, name: string): readonly string[] | undefined;
22
+ /**
23
+ * Editable preview controls for a component:
24
+ *
25
+ * - `variant` → dropdown of declared values
26
+ * - `composite` → dropdown, options from the registered composite
27
+ * - `boolean` → toggle; `string` / `number` → text input
28
+ * - `slot` → text input when string-typed/seeded (`children: 'Badge'`); a
29
+ * ReactNode slot is omitted (its content is a keyed spec element, not a
30
+ * scalar prop — it can't be set from a value control)
31
+ * - `styleProp` / `function` → omitted from the main walk (the Box style
32
+ * surface would flood; event handlers aren't value-editable)
33
+ * - any scalar `preview.defaultProps` entry not already covered (e.g.
34
+ * `children` on a primitive) → text/toggle, or a dropdown when the prop is
35
+ * option-backed (e.g. a `color` styleProp default → token dropdown)
36
+ *
37
+ * `seed` is the rendered instance's current root props (the authored example /
38
+ * live override); a seed value wins over `defaultProps` for the shown default.
39
+ * Variant/slot stay unset when nothing seeds them so the row reads "Default".
40
+ * Sorted variant-first, then the rest, each group alphabetized.
41
+ */
42
+ declare function derivePreviewControls(config: Config, component: Component, seed?: Record<string, unknown>): PreviewControl[];
43
+ //#endregion
44
+ export { PreviewControl, PreviewControlKind, derivePreviewControls, propOptions };
@@ -0,0 +1,139 @@
1
+ //#region src/spec/preview-controls.ts
2
+ /**
3
+ * The selectable values for a prop, or `undefined` when it isn't option-backed.
4
+ * Resolves by the prop's declared kind: a `variant()`'s declared values, a
5
+ * `composite()`'s registered style keys (e.g. `textVariant` → its variant
6
+ * names), or a `styleProp()`'s token + keyword names. A name that isn't a
7
+ * declared prop falls back to a same-named registered style prop (the Box
8
+ * surface — e.g. `color`/`bg` set only via `preview.defaultProps`).
9
+ */
10
+ function propOptions(config, component, name) {
11
+ const info = component.derived.props.find((p) => p.name === name);
12
+ if (!info) return optionsForStyleProp(config, name);
13
+ switch (info.kind) {
14
+ case "variant": return info.values.map(String);
15
+ case "composite": {
16
+ const composite = config.compositeStyles.get(info.compositeName);
17
+ return composite ? [...composite.styles.keys()] : void 0;
18
+ }
19
+ case "styleProp": return optionsForStyleProp(config, info.stylePropName);
20
+ default: return;
21
+ }
22
+ }
23
+ function optionsForStyleProp(config, propName) {
24
+ const styleProp = config.styleProps.get(propName);
25
+ if (!styleProp) return void 0;
26
+ const values = [];
27
+ for (const token of styleProp.derived.tokens) values.push(token.name);
28
+ for (const key of styleProp.derived.keywords.keys()) if (typeof key === "string") values.push(key);
29
+ return values.length > 0 ? values : void 0;
30
+ }
31
+ /**
32
+ * Editable preview controls for a component:
33
+ *
34
+ * - `variant` → dropdown of declared values
35
+ * - `composite` → dropdown, options from the registered composite
36
+ * - `boolean` → toggle; `string` / `number` → text input
37
+ * - `slot` → text input when string-typed/seeded (`children: 'Badge'`); a
38
+ * ReactNode slot is omitted (its content is a keyed spec element, not a
39
+ * scalar prop — it can't be set from a value control)
40
+ * - `styleProp` / `function` → omitted from the main walk (the Box style
41
+ * surface would flood; event handlers aren't value-editable)
42
+ * - any scalar `preview.defaultProps` entry not already covered (e.g.
43
+ * `children` on a primitive) → text/toggle, or a dropdown when the prop is
44
+ * option-backed (e.g. a `color` styleProp default → token dropdown)
45
+ *
46
+ * `seed` is the rendered instance's current root props (the authored example /
47
+ * live override); a seed value wins over `defaultProps` for the shown default.
48
+ * Variant/slot stay unset when nothing seeds them so the row reads "Default".
49
+ * Sorted variant-first, then the rest, each group alphabetized.
50
+ */
51
+ function derivePreviewControls(config, component, seed = {}) {
52
+ const controls = [];
53
+ const seen = /* @__PURE__ */ new Set();
54
+ const declaredDefaults = component.defaultProps ?? {};
55
+ for (const info of component.derived.props) {
56
+ const control = describeProp(config, component, info, declaredDefaults[info.name], seed[info.name]);
57
+ if (!control) continue;
58
+ controls.push(control);
59
+ seen.add(info.name);
60
+ }
61
+ const previewDefaults = component.preview?.defaultProps ?? {};
62
+ for (const [name, value] of Object.entries(previewDefaults)) {
63
+ if (seen.has(name)) continue;
64
+ const options = propOptions(config, component, name);
65
+ if (options && options.length > 0) {
66
+ controls.push({
67
+ name,
68
+ kind: "variant",
69
+ defaultValue: seed[name] ?? value,
70
+ options
71
+ });
72
+ seen.add(name);
73
+ continue;
74
+ }
75
+ const kind = scalarKind(value);
76
+ if (!kind) continue;
77
+ controls.push({
78
+ name,
79
+ kind,
80
+ defaultValue: seed[name] ?? value
81
+ });
82
+ seen.add(name);
83
+ }
84
+ return sortPreviewControls(controls);
85
+ }
86
+ function describeProp(config, component, info, declaredDefault, seed) {
87
+ switch (info.kind) {
88
+ case "variant": return {
89
+ name: info.name,
90
+ kind: "variant",
91
+ defaultValue: seed ?? declaredDefault ?? void 0,
92
+ options: info.values.map(String)
93
+ };
94
+ case "boolean": return {
95
+ name: info.name,
96
+ kind: "boolean",
97
+ defaultValue: seed ?? declaredDefault ?? false
98
+ };
99
+ case "string":
100
+ case "number": return {
101
+ name: info.name,
102
+ kind: "string",
103
+ defaultValue: seed ?? declaredDefault ?? ""
104
+ };
105
+ case "composite": {
106
+ const options = propOptions(config, component, info.name);
107
+ if (!options || options.length === 0) return null;
108
+ return {
109
+ name: info.name,
110
+ kind: "variant",
111
+ defaultValue: seed ?? declaredDefault ?? void 0,
112
+ options
113
+ };
114
+ }
115
+ case "slot": {
116
+ const value = seed ?? declaredDefault;
117
+ if (info.authoredType === "string" || typeof value === "string") return {
118
+ name: info.name,
119
+ kind: "string",
120
+ defaultValue: typeof value === "string" ? value : ""
121
+ };
122
+ return null;
123
+ }
124
+ default: return null;
125
+ }
126
+ }
127
+ function scalarKind(value) {
128
+ if (typeof value === "boolean") return "boolean";
129
+ if (typeof value === "string" || typeof value === "number") return "string";
130
+ return null;
131
+ }
132
+ function sortPreviewControls(controls) {
133
+ const byName = (a, b) => a.name.localeCompare(b.name);
134
+ const variants = controls.filter((c) => c.kind === "variant").sort(byName);
135
+ const rest = controls.filter((c) => c.kind !== "variant").sort(byName);
136
+ return [...variants, ...rest];
137
+ }
138
+ //#endregion
139
+ export { derivePreviewControls, propOptions };
@@ -0,0 +1,39 @@
1
+ //#region src/spec/slot-refs.d.ts
2
+ /**
3
+ * `$slot` resolution utilities for json-render specs.
4
+ *
5
+ * UDS extends json-render's prop-value markers with a `$slot` reference
6
+ * that points to another element in the same Spec's `elements` map:
7
+ *
8
+ * { "$slot": "btn1" } // single ref
9
+ * { "$slot": ["btn1", "btn2"] } // multi-ref (e.g. Card.actions)
10
+ *
11
+ * Used for composition patterns where a slot prop on one component
12
+ * (Card.actions, Button.startIcon) receives whole sub-trees of other
13
+ * elements. The shared helpers here are consumed by:
14
+ *
15
+ * - `wrapSlotResolution` (renderer wrapper) — resolves $slot refs to
16
+ * rendered React children at paint time.
17
+ * - `prepareRenderSpec` (studio) — injects $slot-referenced keys
18
+ * into the parent's `children` array so json-render's `Renderer`
19
+ * traverses them (otherwise an unreferenced element in the
20
+ * `elements` map is never visited).
21
+ * - Inspection / debug surfaces (layers panel, dev code panel,
22
+ * control inference, RPC routes) that need to skip past $slot
23
+ * prop markers when inspecting "real" prop values.
24
+ */
25
+ declare function isSlotRef(value: unknown): value is {
26
+ $slot: string | string[];
27
+ };
28
+ /**
29
+ * Walk a Spec's `elements` map and return, for each parent element
30
+ * that has at least one `$slot`-prop reference, the set of element
31
+ * keys it references. Used by render-time and edit-time code that
32
+ * needs to know "which sibling elements does this parent pull in
33
+ * through slot props."
34
+ */
35
+ declare function collectSlotRefs(elements: Record<string, {
36
+ props?: Record<string, unknown>;
37
+ } | undefined>): Map<string, Set<string>>;
38
+ //#endregion
39
+ export { collectSlotRefs, isSlotRef };
@@ -0,0 +1,56 @@
1
+ //#region src/spec/slot-refs.ts
2
+ /**
3
+ * `$slot` resolution utilities for json-render specs.
4
+ *
5
+ * UDS extends json-render's prop-value markers with a `$slot` reference
6
+ * that points to another element in the same Spec's `elements` map:
7
+ *
8
+ * { "$slot": "btn1" } // single ref
9
+ * { "$slot": ["btn1", "btn2"] } // multi-ref (e.g. Card.actions)
10
+ *
11
+ * Used for composition patterns where a slot prop on one component
12
+ * (Card.actions, Button.startIcon) receives whole sub-trees of other
13
+ * elements. The shared helpers here are consumed by:
14
+ *
15
+ * - `wrapSlotResolution` (renderer wrapper) — resolves $slot refs to
16
+ * rendered React children at paint time.
17
+ * - `prepareRenderSpec` (studio) — injects $slot-referenced keys
18
+ * into the parent's `children` array so json-render's `Renderer`
19
+ * traverses them (otherwise an unreferenced element in the
20
+ * `elements` map is never visited).
21
+ * - Inspection / debug surfaces (layers panel, dev code panel,
22
+ * control inference, RPC routes) that need to skip past $slot
23
+ * prop markers when inspecting "real" prop values.
24
+ */
25
+ function isSlotRef(value) {
26
+ if (typeof value !== "object" || value === null) return false;
27
+ if (!("$slot" in value)) return false;
28
+ const inner = value.$slot;
29
+ return typeof inner === "string" || Array.isArray(inner);
30
+ }
31
+ /**
32
+ * Walk a Spec's `elements` map and return, for each parent element
33
+ * that has at least one `$slot`-prop reference, the set of element
34
+ * keys it references. Used by render-time and edit-time code that
35
+ * needs to know "which sibling elements does this parent pull in
36
+ * through slot props."
37
+ */
38
+ function collectSlotRefs(elements) {
39
+ const parentToSlotKeys = /* @__PURE__ */ new Map();
40
+ for (const [key, el] of Object.entries(elements)) {
41
+ if (!el?.props) continue;
42
+ for (const value of Object.values(el.props)) {
43
+ if (!isSlotRef(value)) continue;
44
+ const refs = Array.isArray(value.$slot) ? value.$slot : [value.$slot];
45
+ let slotKeys = parentToSlotKeys.get(key);
46
+ if (!slotKeys) {
47
+ slotKeys = /* @__PURE__ */ new Set();
48
+ parentToSlotKeys.set(key, slotKeys);
49
+ }
50
+ for (const ref of refs) slotKeys.add(ref);
51
+ }
52
+ }
53
+ return parentToSlotKeys;
54
+ }
55
+ //#endregion
56
+ export { collectSlotRefs, isSlotRef };
@@ -0,0 +1,35 @@
1
+ import { AssetComponentMap } from "./asset-jsx.js";
2
+ import { Spec } from "@json-render/core";
3
+
4
+ //#region src/spec/specToJsx.d.ts
5
+ interface SpecToJsxOptions {
6
+ /** Component import source (default: './uds/components') */
7
+ importSource?: string;
8
+ /**
9
+ * When true, skip the leading `import { ... } from '...';` line and
10
+ * return only the JSX expression in `jsx`. Callers that need to
11
+ * assemble bespoke import lines (e.g. per-component kebab-cased
12
+ * paths for downloadable file exports) can read the type names
13
+ * from the returned `imports` array.
14
+ */
15
+ omitImports?: boolean;
16
+ /**
17
+ * `asset group slug → component tag used for JSX export`
18
+ * (e.g. `{ icons: 'Icon' }`).
19
+ *
20
+ * This is derived from `Config.assetGroups[*].component`. It does not affect
21
+ * asset registration or rendering; it only controls whether a stored asset node
22
+ * like `yahoo-os:asset:icons/Trophy` prints as `<Icon name="Trophy" />`
23
+ * instead of the fallback `<Trophy />`.
24
+ */
25
+ assetComponents?: AssetComponentMap;
26
+ }
27
+ interface SpecToJsxResult {
28
+ /** Generated JSX source code */
29
+ jsx: string;
30
+ /** Component names used (for imports) */
31
+ imports: string[];
32
+ }
33
+ declare function specToJsx(spec: Spec, options?: SpecToJsxOptions): SpecToJsxResult;
34
+ //#endregion
35
+ export { SpecToJsxOptions, SpecToJsxResult, specToJsx };
@@ -0,0 +1,127 @@
1
+ import { isSlotRef } from "./slot-refs.js";
2
+ import { assetJsxForm } from "./asset-jsx.js";
3
+ import { collectUsedComponents, serializeProps } from "@json-render/codegen";
4
+ //#region src/spec/specToJsx.ts
5
+ function statePathToExpression(path) {
6
+ return `state${path.split("/").filter(Boolean).map((s) => `.${s}`).join("")}`;
7
+ }
8
+ function isDynamicValue(value) {
9
+ return typeof value === "object" && value !== null && "$state" in value && typeof value.$state === "string";
10
+ }
11
+ /** Collect all element keys referenced via $slot in a spec element's props */
12
+ function collectSlotKeys(props) {
13
+ const keys = /* @__PURE__ */ new Set();
14
+ for (const value of Object.values(props)) if (isSlotRef(value)) {
15
+ const refs = Array.isArray(value.$slot) ? value.$slot : [value.$slot];
16
+ for (const ref of refs) keys.add(ref);
17
+ }
18
+ return keys;
19
+ }
20
+ function resolveDynamicValue(value) {
21
+ if (isDynamicValue(value)) return statePathToExpression(value.$state);
22
+ return String(value);
23
+ }
24
+ function renderActionBinding(binding) {
25
+ if (Array.isArray(binding)) return `{() => { ${binding.map((b) => b.action).map((a) => `${a}()`).join("; ")} }}`;
26
+ return `{${binding.action}}`;
27
+ }
28
+ function eventNameToJsx(name) {
29
+ return `on${name.charAt(0).toUpperCase()}${name.slice(1)}`;
30
+ }
31
+ function renderElement(key, spec, depth, assetComponents) {
32
+ const element = spec.elements[key];
33
+ if (!element) return "";
34
+ const indent = " ".repeat(depth);
35
+ const { children: _children, ...restProps } = element.props ?? {};
36
+ const slotKeys = collectSlotKeys(restProps);
37
+ const slotAttrs = [];
38
+ const propsWithoutSlots = {};
39
+ for (const [propName, propValue] of Object.entries(restProps)) if (isSlotRef(propValue)) {
40
+ const refs = Array.isArray(propValue.$slot) ? propValue.$slot : [propValue.$slot];
41
+ if (refs.length === 1) {
42
+ const rendered = renderElement(refs[0], spec, 0, assetComponents).trim();
43
+ slotAttrs.push(`${propName}={${rendered}}`);
44
+ } else {
45
+ const rendered = refs.map((r) => renderElement(r, spec, 0, assetComponents).trim()).filter(Boolean);
46
+ slotAttrs.push(`${propName}={<>${rendered.join("")}</>}`);
47
+ }
48
+ } else propsWithoutSlots[propName] = propValue;
49
+ const assetForm = assetJsxForm(element.type, assetComponents);
50
+ const tag = assetForm?.tag ?? element.type;
51
+ const nameAttr = assetForm?.nameAttr ? `name="${assetForm.nameAttr}"` : "";
52
+ if (assetForm?.nameAttr) delete propsWithoutSlots.name;
53
+ const staticAttrs = Object.keys(propsWithoutSlots).length > 0 ? serializeProps(propsWithoutSlots) : "";
54
+ const eventAttrs = [];
55
+ if (element.on && typeof element.on === "object") {
56
+ const onRecord = element.on;
57
+ for (const [eventName, binding] of Object.entries(onRecord)) eventAttrs.push(`${eventNameToJsx(eventName)}=${renderActionBinding(binding)}`);
58
+ }
59
+ const dynamicAttrs = [];
60
+ for (const [propName, propValue] of Object.entries(propsWithoutSlots)) if (isDynamicValue(propValue)) dynamicAttrs.push(`${propName}={${resolveDynamicValue(propValue)}}`);
61
+ let propsStr;
62
+ if (dynamicAttrs.length > 0 || slotAttrs.length > 0) {
63
+ const staticOnly = {};
64
+ for (const [k, v] of Object.entries(propsWithoutSlots)) if (!isDynamicValue(v)) staticOnly[k] = v;
65
+ const parts = [
66
+ nameAttr,
67
+ Object.keys(staticOnly).length > 0 ? serializeProps(staticOnly) : "",
68
+ ...dynamicAttrs,
69
+ ...slotAttrs,
70
+ ...eventAttrs
71
+ ].filter(Boolean);
72
+ propsStr = parts.length > 0 ? ` ${parts.join(" ")}` : "";
73
+ } else {
74
+ const parts = [
75
+ nameAttr,
76
+ staticAttrs,
77
+ ...eventAttrs
78
+ ].filter(Boolean);
79
+ propsStr = parts.length > 0 ? ` ${parts.join(" ")}` : "";
80
+ }
81
+ const rawChildren = element.props?.children;
82
+ const textContent = typeof rawChildren === "string" ? rawChildren : null;
83
+ const dynamicChildren = isDynamicValue(rawChildren) ? `{${resolveDynamicValue(rawChildren)}}` : null;
84
+ const inlineContent = textContent ?? dynamicChildren;
85
+ const childKeys = (element.children ?? []).filter((k) => !slotKeys.has(k));
86
+ const hasChildren = childKeys.length > 0 || inlineContent !== null;
87
+ let jsxLine;
88
+ if (!hasChildren) jsxLine = `${indent}<${tag}${propsStr} />`;
89
+ else if (inlineContent !== null && childKeys.length === 0) jsxLine = `${indent}<${tag}${propsStr}>${inlineContent}</${tag}>`;
90
+ else {
91
+ const childLines = [];
92
+ if (inlineContent !== null) childLines.push(`${" ".repeat(depth + 1)}${inlineContent}`);
93
+ for (const childKey of childKeys) {
94
+ if (!spec.elements[childKey]) continue;
95
+ childLines.push(renderElement(childKey, spec, depth + 1, assetComponents));
96
+ }
97
+ jsxLine = `${indent}<${tag}${propsStr}>\n${childLines.join("\n")}\n${indent}</${tag}>`;
98
+ }
99
+ if (element.repeat && typeof element.repeat === "object") {
100
+ const repeat = element.repeat;
101
+ const stateExpr = statePathToExpression(repeat.statePath);
102
+ const keyProp = repeat.key ? `key={item.${repeat.key}}` : "key={index}";
103
+ jsxLine = `${indent}{${stateExpr}.map((item, index) => (\n${jsxLine.replace(new RegExp(`^${indent}`), `${indent} `)}\n${indent}))}`.replace(`<${tag}`, `<${tag} ${keyProp}`);
104
+ }
105
+ if (element.visible != null) jsxLine = `${indent}{${isDynamicValue(element.visible) ? resolveDynamicValue(element.visible) : String(element.visible)} && (\n${jsxLine.replace(new RegExp(`^${indent}`), `${indent} `)}\n${indent})}`;
106
+ return jsxLine;
107
+ }
108
+ function specToJsx(spec, options) {
109
+ const importSource = options?.importSource ?? "./uds/components";
110
+ if (!spec.elements[spec.root]) return {
111
+ jsx: "",
112
+ imports: []
113
+ };
114
+ const assetComponents = options?.assetComponents;
115
+ const sortedImports = [...new Set([...collectUsedComponents(spec)].map((type) => assetJsxForm(type, assetComponents)?.importName ?? type))].sort();
116
+ const jsxTree = renderElement(spec.root, spec, 0, assetComponents);
117
+ if (options?.omitImports) return {
118
+ jsx: jsxTree,
119
+ imports: sortedImports
120
+ };
121
+ return {
122
+ jsx: `${`import { ${sortedImports.join(", ")} } from '${importSource}';`}\n\n${jsxTree}`,
123
+ imports: sortedImports
124
+ };
125
+ }
126
+ //#endregion
127
+ export { specToJsx };
@@ -0,0 +1,66 @@
1
+ import { TokenModifierKey } from "./types.js";
2
+
3
+ //#region src/token-override-rows.d.ts
4
+ type OverrideModeRef = `${string}.${string}`;
5
+ type ModifierTreeValue<TValue> = TValue | {
6
+ value?: TValue;
7
+ [modifier: TokenModifierKey]: ModifierTreeValue<TValue> | undefined;
8
+ };
9
+ type ModifierTree<TValue> = Partial<Record<TokenModifierKey, ModifierTreeValue<TValue>>>;
10
+ type ModifierUpdateValue<TValue> = TValue | null | ModifierUpdateObject<TValue>;
11
+ interface ModifierUpdateObject<TValue> {
12
+ value?: TValue | null;
13
+ [key: string]: ModifierUpdateValue<TValue> | undefined;
14
+ [modifier: TokenModifierKey]: ModifierUpdateValue<TValue> | undefined;
15
+ }
16
+ type ModifierUpdate<TValue> = Record<string, ModifierUpdateValue<TValue> | undefined>;
17
+ interface ModeBackedModifierInput {
18
+ modifier: TokenModifierKey;
19
+ mode: string;
20
+ option: string;
21
+ label?: string;
22
+ modeOrder: number;
23
+ optionOrder: number;
24
+ }
25
+ interface ModeBackedModifierDefinition extends ModeBackedModifierInput {
26
+ modeRef: OverrideModeRef;
27
+ }
28
+ interface ModeBackedModifierLookup {
29
+ byModifier: ReadonlyMap<TokenModifierKey, ModeBackedModifierDefinition>;
30
+ byModeRef: ReadonlyMap<OverrideModeRef, ModeBackedModifierDefinition>;
31
+ }
32
+ interface OverrideRow<TValue = string> {
33
+ modes: OverrideModeRef[];
34
+ value: TValue;
35
+ sourceModifierPath: TokenModifierKey[];
36
+ sourceOrder: number;
37
+ }
38
+ interface ModeBackedModeMetadata {
39
+ name: string;
40
+ options: readonly {
41
+ name: string;
42
+ label?: string;
43
+ }[];
44
+ }
45
+ interface ModeBackedModifierMetadata {
46
+ modifier: TokenModifierKey;
47
+ selector?: unknown;
48
+ }
49
+ declare function createModeBackedModifierLookup(inputs: readonly ModeBackedModifierInput[]): ModeBackedModifierLookup;
50
+ declare function buildModeBackedModifierLookup(modes: readonly ModeBackedModeMetadata[], modifiers: readonly ModeBackedModifierMetadata[]): ModeBackedModifierLookup;
51
+ declare function modifierTreeToOverrideRows<TValue>(modifiers: ModifierTree<TValue> | undefined, lookup: ModeBackedModifierLookup): OverrideRow<TValue>[];
52
+ declare function overrideRowsToModifierTree<TValue>(rows: readonly OverrideRow<TValue>[], lookup: ModeBackedModifierLookup): ModifierTree<TValue>;
53
+ declare function resolveOverrideRowValue<TValue>(baseValue: TValue, rows: readonly Pick<OverrideRow<TValue>, 'modes' | 'value'>[], activeModes: Iterable<OverrideModeRef>): TValue;
54
+ declare function modifierPathToId(path: readonly TokenModifierKey[]): string;
55
+ declare function modifierIdToPath(id: string): TokenModifierKey[];
56
+ declare function buildNestedModifierUpdate<TValue>(modifierPath: readonly TokenModifierKey[], value: ModifierUpdateValue<TValue>): Record<TokenModifierKey, ModifierUpdateValue<TValue>>;
57
+ /**
58
+ * Combines multiple token-modifier update payloads into one patch
59
+ * object. Later updates win on scalar/null leaves; object branches
60
+ * deep-merge so swap operations can delete one nested row and set
61
+ * another nested row under the same parent modifier in a single
62
+ * patch.
63
+ */
64
+ declare function mergeModifierUpdates<TValue>(...updates: readonly ModifierUpdate<TValue>[]): Record<string, ModifierUpdateValue<TValue>>;
65
+ //#endregion
66
+ export { ModeBackedModifierDefinition, ModeBackedModifierInput, ModeBackedModifierLookup, OverrideModeRef, OverrideRow, buildModeBackedModifierLookup, buildNestedModifierUpdate, createModeBackedModifierLookup, mergeModifierUpdates, modifierIdToPath, modifierPathToId, modifierTreeToOverrideRows, overrideRowsToModifierTree, resolveOverrideRowValue };