@yahoo/uds-create-config 2.44.0 → 3.0.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.
- package/README.md +129 -0
- package/dist/ai-lanes.d.ts +23 -0
- package/dist/ai-lanes.js +34 -0
- package/dist/authoring/define-component.d.ts +85 -0
- package/dist/configs/CanvasConfig.d.ts +3237 -0
- package/dist/configs/CanvasConfig.js +332 -0
- package/dist/configs/build-options.d.ts +10 -0
- package/dist/configs/build-options.js +68 -0
- package/dist/configs/platform.d.ts +15 -0
- package/dist/configs/platform.js +17 -0
- package/dist/configs/react-native-system.d.ts +3026 -0
- package/dist/configs/react-native-system.js +64 -0
- package/dist/configs/system.d.ts +2771 -0
- package/dist/configs/system.js +74 -0
- package/dist/css/properties.generated.d.ts +14 -0
- package/dist/css/properties.generated.js +6260 -0
- package/dist/css/value-type.d.ts +48 -0
- package/dist/css/value-type.js +131 -0
- package/dist/css/values.d.ts +33 -0
- package/dist/css/values.js +116 -0
- package/dist/entities/native/NativeModifier.d.ts +25 -0
- package/dist/entities/native/NativeModifier.js +41 -0
- package/dist/entities/native/NativeSettings.d.ts +44 -0
- package/dist/entities/native/NativeSettings.js +39 -0
- package/dist/entities/native/NativeStyleProperty.d.ts +355 -0
- package/dist/entities/native/NativeStyleProperty.js +28 -0
- package/dist/entities/native/NativeToken.d.ts +31 -0
- package/dist/entities/native/NativeToken.js +49 -0
- package/dist/entities/system/CanvasRole.d.ts +34 -0
- package/dist/entities/system/CanvasRole.js +45 -0
- package/dist/entities/system/Component.d.ts +1287 -0
- package/dist/entities/system/Component.js +1840 -0
- package/dist/entities/system/Composite.d.ts +12 -0
- package/dist/entities/system/Composite.js +26 -0
- package/dist/entities/system/Device.d.ts +11 -0
- package/dist/entities/system/Device.js +18 -0
- package/dist/entities/system/File.d.ts +72 -0
- package/dist/entities/system/File.js +136 -0
- package/dist/entities/system/Font.d.ts +48 -0
- package/dist/entities/system/Font.js +56 -0
- package/dist/entities/system/GlobalStyle.d.ts +26 -0
- package/dist/entities/system/GlobalStyle.js +54 -0
- package/dist/entities/system/Guidance.d.ts +85 -0
- package/dist/entities/system/Guidance.js +59 -0
- package/dist/entities/system/GuidanceStyle.d.ts +14 -0
- package/dist/entities/system/GuidanceStyle.js +8 -0
- package/dist/entities/system/Icon.d.ts +46 -0
- package/dist/entities/system/Icon.js +46 -0
- package/dist/entities/system/LinkedSystem.d.ts +504 -0
- package/dist/entities/system/LinkedSystem.js +814 -0
- package/dist/entities/system/Modifier.d.ts +92 -0
- package/dist/entities/system/Modifier.js +120 -0
- package/dist/entities/system/Motion.d.ts +28 -0
- package/dist/entities/system/Motion.js +48 -0
- package/dist/entities/system/Operation.d.ts +100 -0
- package/dist/entities/system/Operation.js +236 -0
- package/dist/entities/system/Package.d.ts +42 -0
- package/dist/entities/system/Package.js +75 -0
- package/dist/entities/system/Settings.d.ts +94 -0
- package/dist/entities/system/Settings.js +166 -0
- package/dist/entities/system/StyleProperty.d.ts +135 -0
- package/dist/entities/system/StyleProperty.js +438 -0
- package/dist/entities/system/Token.d.ts +67 -0
- package/dist/entities/system/Token.js +129 -0
- package/dist/entities/system/Tool.d.ts +134 -0
- package/dist/entities/system/Tool.js +124 -0
- package/dist/entities/system/color.d.ts +64 -0
- package/dist/entities/system/color.js +123 -0
- package/dist/entities/system/component-module.d.ts +63 -0
- package/dist/entities/system/component-module.js +258 -0
- package/dist/entities/system/copy-plan.d.ts +98 -0
- package/dist/entities/system/copy-plan.js +451 -0
- package/dist/entities/system/defineComponent.d.ts +428 -0
- package/dist/entities/system/defineComponent.js +337 -0
- package/dist/entities/system/element.d.ts +101 -0
- package/dist/entities/system/element.js +171 -0
- package/dist/entities/system/icon-library.d.ts +64 -0
- package/dist/entities/system/icon-library.js +112 -0
- package/dist/entities/system/icon-metadata.d.ts +72 -0
- package/dist/entities/system/icon-metadata.js +111 -0
- package/dist/entities/system/link-declaration.d.ts +27 -0
- package/dist/entities/system/link-declaration.js +64 -0
- package/dist/entities/system/link-plan.d.ts +160 -0
- package/dist/entities/system/link-plan.js +770 -0
- package/dist/entities/system/runtime-modules.d.ts +25 -0
- package/dist/entities/system/runtime-modules.js +38 -0
- package/dist/entities/system/style-bag.d.ts +20 -0
- package/dist/entities/system/style-bag.js +246 -0
- package/dist/entities/system/token-index.d.ts +89 -0
- package/dist/entities/system/token-index.js +112 -0
- package/dist/framework/Config.d.ts +2472 -0
- package/dist/framework/Config.js +5023 -0
- package/dist/framework/Entity.d.ts +127 -0
- package/dist/framework/Entity.js +125 -0
- package/dist/framework/authoring.d.ts +24 -0
- package/dist/framework/authoring.js +7 -0
- package/dist/framework/change-hooks.d.ts +119 -0
- package/dist/framework/change-hooks.js +131 -0
- package/dist/framework/changes.d.ts +88 -0
- package/dist/framework/changes.js +246 -0
- package/dist/framework/class-names.d.ts +260 -0
- package/dist/framework/class-names.js +630 -0
- package/dist/framework/coalesce.d.ts +25 -0
- package/dist/framework/coalesce.js +60 -0
- package/dist/framework/config-op.d.ts +76 -0
- package/dist/framework/config-op.js +43 -0
- package/dist/framework/config-operation-kind.d.ts +14 -0
- package/dist/framework/config-operation-kind.js +14 -0
- package/dist/framework/config-ops.d.ts +34 -0
- package/dist/framework/config-ops.js +187 -0
- package/dist/framework/defineConfig.d.ts +196 -0
- package/dist/framework/defineConfig.js +100 -0
- package/dist/framework/defineDerivedEntity.d.ts +74 -0
- package/dist/framework/defineDerivedEntity.js +83 -0
- package/dist/framework/defineEntity.d.ts +318 -0
- package/dist/framework/defineEntity.js +183 -0
- package/dist/framework/defineSubEntity.d.ts +148 -0
- package/dist/framework/defineSubEntity.js +121 -0
- package/dist/framework/derived-mutations.d.ts +20 -0
- package/dist/framework/derived-mutations.js +38 -0
- package/dist/framework/entity-search.d.ts +29 -0
- package/dist/framework/entity-search.js +117 -0
- package/dist/framework/inline-sources.d.ts +49 -0
- package/dist/framework/inline-sources.js +388 -0
- package/dist/framework/layer-styles.d.ts +25 -0
- package/dist/framework/layer-styles.js +49 -0
- package/dist/framework/memo.d.ts +48 -0
- package/dist/framework/memo.js +84 -0
- package/dist/framework/overlay.d.ts +49 -0
- package/dist/framework/overlay.js +17 -0
- package/dist/framework/plan.d.ts +49 -0
- package/dist/framework/plan.js +73 -0
- package/dist/framework/projections.d.ts +480 -0
- package/dist/framework/projections.js +974 -0
- package/dist/framework/prop-surface.d.ts +132 -0
- package/dist/framework/prop-surface.js +278 -0
- package/dist/framework/ref-graph.d.ts +71 -0
- package/dist/framework/ref-graph.js +158 -0
- package/dist/framework/ref-integrity.d.ts +55 -0
- package/dist/framework/ref-integrity.js +171 -0
- package/dist/framework/registered.d.ts +95 -0
- package/dist/framework/registry-dir.d.ts +42 -0
- package/dist/framework/registry-dir.js +48 -0
- package/dist/framework/registry.d.ts +300 -0
- package/dist/framework/registry.js +104 -0
- package/dist/framework/rejection.d.ts +58 -0
- package/dist/framework/rejection.js +111 -0
- package/dist/framework/rename-inference.d.ts +26 -0
- package/dist/framework/rename-inference.js +50 -0
- package/dist/framework/render-spec.d.ts +296 -0
- package/dist/framework/render-spec.js +809 -0
- package/dist/framework/schema-version.d.ts +140 -0
- package/dist/framework/schema-version.js +214 -0
- package/dist/framework/schemas.d.ts +109 -0
- package/dist/framework/schemas.js +615 -0
- package/dist/framework/session.d.ts +196 -0
- package/dist/framework/session.js +367 -0
- package/dist/framework/signature.d.ts +15 -0
- package/dist/framework/signature.js +83 -0
- package/dist/framework/snapshot.d.ts +115 -0
- package/dist/framework/snapshot.js +277 -0
- package/dist/framework/source-integrity.d.ts +226 -0
- package/dist/framework/source-integrity.js +478 -0
- package/dist/framework/sources.d.ts +211 -0
- package/dist/framework/sources.js +89 -0
- package/dist/framework/utils/compiled-schema.d.ts +23 -0
- package/dist/framework/utils/compiled-schema.js +46 -0
- package/dist/framework/utils/content-key.d.ts +15 -0
- package/dist/framework/utils/content-key.js +33 -0
- package/dist/framework/utils/enumerated.d.ts +13 -0
- package/dist/framework/utils/enumerated.js +19 -0
- package/dist/framework/utils/field-path.d.ts +95 -0
- package/dist/framework/utils/field-path.js +136 -0
- package/dist/framework/utils/field-schema.d.ts +55 -0
- package/dist/framework/utils/field-schema.js +183 -0
- package/dist/framework/utils/group.d.ts +37 -0
- package/dist/framework/utils/group.js +13 -0
- package/dist/framework/utils/package-path.d.ts +37 -0
- package/dist/framework/utils/package-path.js +61 -0
- package/dist/framework/utils/refs.d.ts +194 -0
- package/dist/framework/utils/refs.js +403 -0
- package/dist/framework/validate-spec.d.ts +27 -0
- package/dist/framework/validate-spec.js +45 -0
- package/dist/framework/value-domain.d.ts +26 -0
- package/dist/framework/value-domain.js +202 -0
- package/dist/framework/views-facade.d.ts +15 -0
- package/dist/framework/views-facade.js +19 -0
- package/dist/index.d.ts +90 -42
- package/dist/index.js +86 -37
- package/dist/migrations/2.0.0/raw-css.d.ts +11 -0
- package/dist/migrations/2.0.0/raw-css.js +72 -0
- package/dist/migrations/2.0.0/v1-artifact.d.ts +1559 -0
- package/dist/migrations/2.0.0/v1-artifact.js +1018 -0
- package/dist/migrations/20260908171448_settings_as_kind.d.ts +22 -0
- package/dist/migrations/20260908171448_settings_as_kind.js +89 -0
- package/dist/migrations/20260908194245_visible_state_pointer.d.ts +19 -0
- package/dist/migrations/20260908194245_visible_state_pointer.js +115 -0
- package/dist/migrations/20260909183001_icon_knockout_variants.d.ts +18 -0
- package/dist/migrations/20260909183001_icon_knockout_variants.js +8 -0
- package/dist/migrations/20260910212453_linked_system_link_ops.d.ts +13 -0
- package/dist/migrations/20260910212453_linked_system_link_ops.js +8 -0
- package/dist/migrations/index.d.ts +2 -0
- package/dist/migrations/index.js +2 -0
- package/dist/react-native/style-keys.generated.d.ts +10 -0
- package/dist/react-native/style-keys.generated.js +167 -0
- package/dist/renderer/RendererErrorBoundary.d.ts +32 -5
- package/dist/renderer/RendererErrorBoundary.js +59 -10
- package/dist/renderer/UdsRenderer.d.ts +11 -10
- package/dist/renderer/UdsRenderer.js +1 -1
- package/dist/renderer/assetEntries.d.ts +37 -0
- package/dist/renderer/assetEntries.js +39 -0
- package/dist/renderer/assetRenderable.d.ts +14 -6
- package/dist/renderer/assetRenderable.js +8 -2
- package/dist/renderer/componentEntry.d.ts +58 -0
- package/dist/renderer/componentEntry.js +71 -0
- package/dist/renderer/icon-library.d.ts +20 -0
- package/dist/renderer/index.d.ts +7 -7
- package/dist/renderer/index.js +5 -5
- package/dist/renderer/primitives/SlotRenderer.d.ts +1 -1
- package/dist/renderer/primitives/SlotRenderer.js +1 -1
- package/dist/renderer/runtimeComponent.d.ts +33 -0
- package/dist/renderer/runtimeComponent.js +113 -0
- package/dist/renderer/spec-content.d.ts +43 -0
- package/dist/renderer/spec-content.js +95 -0
- package/dist/renderer/wrapRegistry.d.ts +22 -37
- package/dist/renderer/wrapRegistry.js +10 -20
- package/dist/renderer/wrappers/event-bridge.js +6 -5
- package/dist/renderer/wrappers/hex-normalize.js +6 -5
- package/dist/renderer/wrappers/html-aliases.d.ts +7 -2
- package/dist/renderer/wrappers/html-aliases.js +36 -19
- package/dist/renderer/wrappers/inline-styles.js +7 -6
- package/dist/spec/asset-jsx.d.ts +18 -5
- package/dist/spec/asset-jsx.js +27 -16
- package/dist/spec/collapse-text-labels.d.ts +5 -15
- package/dist/spec/collapse-text-labels.js +26 -19
- package/dist/spec/empty-node-slots.d.ts +56 -34
- package/dist/spec/empty-node-slots.js +87 -47
- package/dist/spec/index.d.ts +21 -10
- package/dist/spec/index.js +21 -10
- package/dist/spec/jsxToSpec.d.ts +14 -9
- package/dist/spec/jsxToSpec.js +20 -3
- package/dist/spec/preview-elements.d.ts +63 -0
- package/dist/spec/preview-elements.js +89 -0
- package/dist/spec/rewrite-refs.d.ts +58 -0
- package/dist/spec/rewrite-refs.js +250 -0
- package/dist/spec/specToJsx.js +18 -29
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +39 -52
- package/dist/AssetGroup.d.ts +0 -77
- package/dist/AssetGroup.js +0 -125
- package/dist/Component.d.ts +0 -333
- package/dist/Component.js +0 -1061
- package/dist/ComponentGroup.d.ts +0 -22
- package/dist/ComponentGroup.js +0 -51
- package/dist/CompositeStyle.d.ts +0 -30
- package/dist/CompositeStyle.js +0 -59
- package/dist/Config.d.ts +0 -528
- package/dist/Config.js +0 -1712
- package/dist/Mode.d.ts +0 -41
- package/dist/Mode.js +0 -81
- package/dist/Modifier.d.ts +0 -51
- package/dist/Modifier.js +0 -97
- package/dist/MotionDef.d.ts +0 -81
- package/dist/MotionDef.js +0 -212
- package/dist/Props.d.ts +0 -332
- package/dist/Props.js +0 -35
- package/dist/Provider.d.ts +0 -20
- package/dist/Provider.js +0 -14
- package/dist/StyleProp.d.ts +0 -145
- package/dist/StyleProp.js +0 -239
- package/dist/Token.d.ts +0 -76
- package/dist/Token.js +0 -133
- package/dist/TokenGroup.d.ts +0 -33
- package/dist/TokenGroup.js +0 -69
- package/dist/asset-kind.d.ts +0 -55
- package/dist/asset-kind.js +0 -29
- package/dist/asset-value.d.ts +0 -23
- package/dist/asset-value.js +0 -40
- package/dist/brands.d.ts +0 -30
- package/dist/brands.js +0 -20
- package/dist/captureCallerPath.d.ts +0 -48
- package/dist/captureCallerPath.js +0 -95
- package/dist/colorExpressions.d.ts +0 -131
- package/dist/colorExpressions.js +0 -148
- package/dist/component-referrers.d.ts +0 -30
- package/dist/component-referrers.js +0 -40
- package/dist/config-diagnostics.d.ts +0 -21
- package/dist/config-diagnostics.js +0 -12
- package/dist/defineAssetGroup.d.ts +0 -200
- package/dist/defineAssetGroup.js +0 -323
- package/dist/defineProvider.d.ts +0 -29
- package/dist/defineProvider.js +0 -60
- package/dist/element-marker.d.ts +0 -63
- package/dist/element-marker.js +0 -113
- package/dist/entity-utils.d.ts +0 -56
- package/dist/entity-utils.js +0 -105
- package/dist/extension.d.ts +0 -55
- package/dist/extension.js +0 -214
- package/dist/factories.d.ts +0 -863
- package/dist/factories.js +0 -475
- package/dist/font-face.d.ts +0 -25
- package/dist/font-face.js +0 -72
- package/dist/foreign-component-name.d.ts +0 -21
- package/dist/foreign-component-name.js +0 -42
- package/dist/interpolate.d.ts +0 -20
- package/dist/interpolate.js +0 -10
- package/dist/jsx/__fixtures__/cross-component-preview.d.ts +0 -3
- package/dist/jsx/__fixtures__/cross-component-preview.js +0 -15
- package/dist/jsx/jsx-dev-runtime.d.ts +0 -15
- package/dist/jsx/jsx-dev-runtime.js +0 -11
- package/dist/jsx/jsx-runtime.d.ts +0 -48
- package/dist/jsx/jsx-runtime.js +0 -305
- package/dist/linked-system-pins.d.ts +0 -30
- package/dist/linked-system-pins.js +0 -45
- package/dist/linkedNodePrefixes.d.ts +0 -73
- package/dist/linkedNodePrefixes.js +0 -99
- package/dist/markers.d.ts +0 -235
- package/dist/markers.js +0 -70
- package/dist/motion-constants.d.ts +0 -37
- package/dist/motion-constants.js +0 -61
- package/dist/native/NativeConfig.d.ts +0 -56
- package/dist/native/NativeConfig.js +0 -162
- package/dist/native/assets.d.ts +0 -20
- package/dist/native/assets.js +0 -17
- package/dist/native/components.d.ts +0 -139
- package/dist/native/components.js +0 -67
- package/dist/native/index.d.ts +0 -18
- package/dist/native/index.js +0 -17
- package/dist/native/modes.d.ts +0 -10
- package/dist/native/modes.js +0 -11
- package/dist/native/types.d.ts +0 -95
- package/dist/native/types.js +0 -0
- package/dist/native/validation.d.ts +0 -8
- package/dist/native/validation.js +0 -885
- package/dist/native/values.d.ts +0 -7
- package/dist/native/values.js +0 -15
- package/dist/native.d.ts +0 -12
- package/dist/native.js +0 -10
- package/dist/refs.d.ts +0 -204
- package/dist/refs.js +0 -129
- package/dist/registry-aliases.d.ts +0 -33
- package/dist/registry-aliases.js +0 -35
- package/dist/registry-dir.d.ts +0 -22
- package/dist/registry-dir.js +0 -10
- package/dist/renderer/makeRegistry.d.ts +0 -34
- package/dist/renderer/makeRegistry.js +0 -52
- package/dist/renderer/makeUdsRenderer.d.ts +0 -13
- package/dist/renderer/makeUdsRenderer.js +0 -51
- package/dist/renderer/wrappers/component-slots.d.ts +0 -41
- package/dist/renderer/wrappers/component-slots.js +0 -66
- package/dist/renderer/wrappers/slot-resolution.d.ts +0 -25
- package/dist/renderer/wrappers/slot-resolution.js +0 -68
- package/dist/renderer/wrappers/void-elements.d.ts +0 -37
- package/dist/renderer/wrappers/void-elements.js +0 -40
- package/dist/spec/layer-props.d.ts +0 -52
- package/dist/spec/layer-props.js +0 -149
- package/dist/spec/preview-controls.d.ts +0 -44
- package/dist/spec/preview-controls.js +0 -139
- package/dist/spec/slot-refs.d.ts +0 -39
- package/dist/spec/slot-refs.js +0 -56
- package/dist/token-override-rows.d.ts +0 -68
- package/dist/token-override-rows.js +0 -233
- package/dist/tokenValueType.d.ts +0 -34
- package/dist/tokenValueType.js +0 -138
- package/dist/types/css-properties.d.ts +0 -233
- package/dist/types/css-properties.js +0 -14
- package/dist/types/css-property-keywords.d.ts +0 -156
- package/dist/types/css-property-keywords.js +0 -616
- package/dist/types/css-values.d.ts +0 -63
- package/dist/types/css-values.js +0 -16
- package/dist/types.d.ts +0 -1138
- package/dist/types.js +0 -12
- package/dist/units.d.ts +0 -14
- package/dist/units.js +0 -16
- package/dist/utils/index.d.ts +0 -4
- package/dist/utils/index.js +0 -4
|
@@ -0,0 +1,974 @@
|
|
|
1
|
+
import { isRef, kindOf, memberOf, pathOf, ref, refLeaf, sourceOf, splitRef } from "./utils/refs.js";
|
|
2
|
+
import { isDerivedColor, isGradient, renderDerivedColor, renderGradient } from "../entities/system/color.js";
|
|
3
|
+
import { forwardsOf } from "../entities/system/element.js";
|
|
4
|
+
import { listedIn } from "./utils/enumerated.js";
|
|
5
|
+
import { modifierCategory } from "../entities/system/Modifier.js";
|
|
6
|
+
import { classPrefixOf, varPrefixOf } from "../entities/system/Settings.js";
|
|
7
|
+
import { StyleProperty, leavesOfStylePropValue } from "../entities/system/StyleProperty.js";
|
|
8
|
+
//#region src/framework/projections.ts
|
|
9
|
+
/**
|
|
10
|
+
* Expanded value domains, cached per config instance.
|
|
11
|
+
*
|
|
12
|
+
* A domain is read once per authored value, not once per property: `resolveComponentStyleModel` asks
|
|
13
|
+
* `stylePropTokenPath` for every leaf a property offers, and each of those re-expands the whole
|
|
14
|
+
* domain behind it. Over this system's own artifact one CSS emit expanded the token domain 23,895
|
|
15
|
+
* times — 16M `withComputed` reads and 1.7s, on the browser's main thread, per config change.
|
|
16
|
+
*
|
|
17
|
+
* Keyed on the config rather than on its snapshot slices, which is the stronger key: a config is a
|
|
18
|
+
* value, so its snapshot and its attached sources are both fixed for the life of the instance, and a
|
|
19
|
+
* domain reached through a link (`Token.ref('color@yahoo-os')`) is answered by a source config that a
|
|
20
|
+
* slice key cannot see. A hit therefore cannot be stale, and an edit — which allocates a new config —
|
|
21
|
+
* gets a fresh cache, which is exactly the intent: the win here is within one derive.
|
|
22
|
+
*
|
|
23
|
+
* Both arrays are shared with every other caller of that config: consume them, don't mutate them.
|
|
24
|
+
*/
|
|
25
|
+
const memberEntryCache = /* @__PURE__ */ new WeakMap();
|
|
26
|
+
const stylePropEntryCache = /* @__PURE__ */ new WeakMap();
|
|
27
|
+
/** The per-config half of one of the caches above, created on first use. */
|
|
28
|
+
function domainCache(cache, config) {
|
|
29
|
+
let entry = cache.get(config);
|
|
30
|
+
if (!entry) {
|
|
31
|
+
entry = /* @__PURE__ */ new Map();
|
|
32
|
+
cache.set(config, entry);
|
|
33
|
+
}
|
|
34
|
+
return entry;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The variable namespace a linked system's entities are declared under, seen from `config`.
|
|
38
|
+
*
|
|
39
|
+
* Not the source's own prefix. That is a setting the source chose for itself and the one it gets for
|
|
40
|
+
* free is `uds`, so two pinned systems that both took the default would declare the same
|
|
41
|
+
* `--uds-color-brand` and the later `:root` would win — every ref through either pin painting one
|
|
42
|
+
* value, silently, because an over-declared custom property is not an error.
|
|
43
|
+
*
|
|
44
|
+
* The slug is the name that cannot collide: it is a key in the consuming config, unique there by
|
|
45
|
+
* construction, and already the only name resolution uses. So a borrowed variable lives in the
|
|
46
|
+
* consumer's namespace, qualified by the pin it came through.
|
|
47
|
+
*/
|
|
48
|
+
const sourceVarPrefix = (config, slug) => `${varPrefixOf(config)}-${kebab(slug)}`;
|
|
49
|
+
/** camelCase → kebab, letter→number split, non-alphanumeric runs collapsed, lowercased. The rule
|
|
50
|
+
* the emitted CSS uses for a token's group segment (`borderColor` → `border-color`), so a
|
|
51
|
+
* reference built here resolves against the stylesheet instead of naming a property that was
|
|
52
|
+
* never declared. */
|
|
53
|
+
function kebab(segment) {
|
|
54
|
+
return segment.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([a-z])([0-9]+)/gi, "$1-$2").replace(/[^A-Za-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").toLowerCase();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The CSS custom-property name for an entity — `--<prefix>-<group-kebabed>-<leaf>`.
|
|
58
|
+
*
|
|
59
|
+
* The group segment is kebab-cased and the leaf is not, which is not a stylistic choice: it's the
|
|
60
|
+
* shape the emitted stylesheet declares (`--{prefix}-{kebabCase(namespace)}-{safeTokenName(name)}`).
|
|
61
|
+
* Deriving it any other way produces a `var()` that resolves to nothing for any camelCase group.
|
|
62
|
+
*/
|
|
63
|
+
function cssVar(config, _kind, path, varPrefix) {
|
|
64
|
+
const slash = path.indexOf("/");
|
|
65
|
+
const prefix = varPrefix ?? varPrefixOf(config);
|
|
66
|
+
if (slash <= 0 || slash >= path.length - 1) return `--${prefix}-${kebab(path)}`;
|
|
67
|
+
return `--${prefix}-${kebab(path.slice(0, slash))}-${path.slice(slash + 1).replaceAll(".", "_").replaceAll("/", "-").replace(/\s+/g, "-")}`;
|
|
68
|
+
}
|
|
69
|
+
/** `var(--…)` — the CSS reference to {@link cssVar}. */
|
|
70
|
+
function cssVarRef(config, kind, path, varPrefix) {
|
|
71
|
+
return `var(${cssVar(config, kind, path, varPrefix)})`;
|
|
72
|
+
}
|
|
73
|
+
/** The utility/class name for an entity — `<prefix>-<path-dashed>`. A class is one identifier, so
|
|
74
|
+
* whitespace in the path is written as a hyphen too; a composite or motion preset named with a space
|
|
75
|
+
* otherwise declares a rule no element's class list can match. */
|
|
76
|
+
function className(config, _kind, path) {
|
|
77
|
+
return `${classPrefixOf(config)}-${path.replaceAll("/", "-").replace(/\s+/g, "-")}`;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A style prop's allowed values, flattened to concrete strings: literal keywords (`row`)
|
|
81
|
+
* pass through, and a token group ref (`ref('token', 'color')`) expands to every member
|
|
82
|
+
* Token's path (`color/brand`, `color/accent`, …) — the utility set the prop offers,
|
|
83
|
+
* kept in lockstep with the group instead of restated. A ref that names a single token
|
|
84
|
+
* resolves to just that path. Shared by codegen (utility emit) and the UI value picker so
|
|
85
|
+
* both offer the same set. Order follows the authored `values`, group members in store
|
|
86
|
+
* order. Unknown prop → `[]`.
|
|
87
|
+
*/
|
|
88
|
+
function stylePropValues(config, path) {
|
|
89
|
+
return stylePropEntries(config, path).map((entry) => entry.value);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The entries a ref target offers as `{leaf, path}` — the one leaf↔qualified-path map behind every
|
|
93
|
+
* ref-bound value domain, whatever the ref names:
|
|
94
|
+
*
|
|
95
|
+
* Token:color/brand an item → itself (leaf = its name)
|
|
96
|
+
* Token:color a group → one entry per member (`color/brand` → `brand`)
|
|
97
|
+
* icon:phosphor#variants a facet → one entry per value that field of the group node lists
|
|
98
|
+
*
|
|
99
|
+
* Style-prop values, composite props, a variant bound to a group, and an icon prop drawing its
|
|
100
|
+
* weights from the library it renders are all the same question, so they are all this. A prop that
|
|
101
|
+
* enumerates instead of delegating is a copy of a list nothing keeps in step — 1,530 glyph names in
|
|
102
|
+
* the component that renders them, which is what the facet arm exists to stop.
|
|
103
|
+
*/
|
|
104
|
+
function memberEntries(config, target) {
|
|
105
|
+
const cache = domainCache(memberEntryCache, config);
|
|
106
|
+
const key = isRef(target) ? target.__ref : target;
|
|
107
|
+
const hit = cache.get(key);
|
|
108
|
+
if (hit) return hit;
|
|
109
|
+
const expanded = expandMemberEntries(config, target);
|
|
110
|
+
cache.set(key, expanded);
|
|
111
|
+
return expanded;
|
|
112
|
+
}
|
|
113
|
+
function expandMemberEntries(config, target) {
|
|
114
|
+
const { kind, path, source, member: facet } = splitRef(target);
|
|
115
|
+
/**
|
|
116
|
+
* Whose members these are.
|
|
117
|
+
*
|
|
118
|
+
* A ref with a source names entities in the linked system, so every lookup below has to run
|
|
119
|
+
* against that config with the bare path — asking the consumer for a group called `palette@ds` is
|
|
120
|
+
* asking for something it has never had, and it answered by returning nothing at all. A style prop
|
|
121
|
+
* whose domain was a borrowed group therefore offered no values: no utilities emitted, and
|
|
122
|
+
* `bg="accent"` painted nothing, with no error anywhere.
|
|
123
|
+
*/
|
|
124
|
+
const owner = source === void 0 ? config : config.configFor(target);
|
|
125
|
+
if (!owner) return [];
|
|
126
|
+
/** The value side stays addressable from here, so a member's path keeps its qualifier. */
|
|
127
|
+
const qualify = (memberPath) => source === void 0 ? memberPath : `${memberPath}@${source}`;
|
|
128
|
+
if (facet === void 0 && path === "") return owner.list(kind).map((item) => ({
|
|
129
|
+
leaf: item.path,
|
|
130
|
+
path: qualify(item.path)
|
|
131
|
+
}));
|
|
132
|
+
if (facet !== void 0) {
|
|
133
|
+
const bare = owner.entityOf(kind)?.bareMember;
|
|
134
|
+
if (bare !== void 0 && !facet.includes("/")) {
|
|
135
|
+
const items = owner.resolve(kind, path)?.toJSON()?.[bare];
|
|
136
|
+
if (typeof items === "object" && items !== null && facet in items) return [{
|
|
137
|
+
leaf: facet,
|
|
138
|
+
path: qualify(`${path}#${facet}`)
|
|
139
|
+
}];
|
|
140
|
+
}
|
|
141
|
+
const listed = groupMetaOf(owner, kind, path)?.[facet];
|
|
142
|
+
if (!Array.isArray(listed)) return [];
|
|
143
|
+
return listed.filter((value) => typeof value === "string").map((value) => ({
|
|
144
|
+
leaf: value,
|
|
145
|
+
path: value
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
if (owner.resolve(kind, path)) return [{
|
|
149
|
+
leaf: path.slice(path.lastIndexOf("/") + 1),
|
|
150
|
+
path: qualify(path)
|
|
151
|
+
}];
|
|
152
|
+
const leafOf = (memberPath) => memberPath.startsWith(`${path}/`) ? memberPath.slice(path.length + 1) : memberPath;
|
|
153
|
+
const theirs = owner.members(kind, path);
|
|
154
|
+
if (source === void 0) return theirs.map((member) => ({
|
|
155
|
+
leaf: leafOf(member.path),
|
|
156
|
+
path: member.path
|
|
157
|
+
}));
|
|
158
|
+
const ours = config.members(kind, path);
|
|
159
|
+
const held = new Set(ours.map((member) => member.path));
|
|
160
|
+
const covered = new Set(theirs.map((member) => member.path));
|
|
161
|
+
return [...theirs.map((member) => ({
|
|
162
|
+
leaf: leafOf(member.path),
|
|
163
|
+
path: held.has(member.path) ? member.path : qualify(member.path)
|
|
164
|
+
})), ...ours.filter((member) => !covered.has(member.path)).map((member) => ({
|
|
165
|
+
leaf: leafOf(member.path),
|
|
166
|
+
path: member.path
|
|
167
|
+
}))];
|
|
168
|
+
}
|
|
169
|
+
/** A group node's own body, by path. */
|
|
170
|
+
function groupMetaOf(config, kind, path) {
|
|
171
|
+
return config.groups(kind).find((group) => group.path === path)?.meta;
|
|
172
|
+
}
|
|
173
|
+
/** The leaf value domain a ref offers — for any group-bound prop (composite/variant).
|
|
174
|
+
*
|
|
175
|
+
* Takes the ref itself, not its parts. A caller holding `token:palette@ds` has the whole address; a
|
|
176
|
+
* signature that took it apart made every call site split one by hand, and the three that did
|
|
177
|
+
* disagreed about whether the `@source` survived the trip — which decides which config the members
|
|
178
|
+
* come from. */
|
|
179
|
+
function memberLeaves(config, target) {
|
|
180
|
+
return memberEntries(config, target).map((entry) => entry.leaf);
|
|
181
|
+
}
|
|
182
|
+
/** Resolve a group-bound leaf back to its qualified path (`display2` → `textVariant/display2`).
|
|
183
|
+
* `undefined` when the leaf isn't a member — what validation rejects. */
|
|
184
|
+
function memberFromLeaf(config, target, leaf) {
|
|
185
|
+
return memberEntries(config, target).find((entry) => entry.leaf === leaf)?.path;
|
|
186
|
+
}
|
|
187
|
+
function stylePropEntries(config, path) {
|
|
188
|
+
const cache = domainCache(stylePropEntryCache, config);
|
|
189
|
+
const hit = cache.get(path);
|
|
190
|
+
if (hit) return hit;
|
|
191
|
+
const expanded = expandStylePropEntries(config, path);
|
|
192
|
+
cache.set(path, expanded);
|
|
193
|
+
return expanded;
|
|
194
|
+
}
|
|
195
|
+
function expandStylePropEntries(config, path) {
|
|
196
|
+
const prop = config.resolve("style-property", path);
|
|
197
|
+
if (!prop) return [];
|
|
198
|
+
const values = prop.toJSON().values ?? [];
|
|
199
|
+
const members = (target) => memberEntries(config, target);
|
|
200
|
+
return values.flatMap((entry) => leavesOfStylePropValue(entry, members));
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* A style prop's leaf value domain — what a consumer/model actually authors (`warning`, `carbon/2`),
|
|
204
|
+
* not the qualified storage path (`color/warning`). This is what the catalog, `uds_get_component`,
|
|
205
|
+
* `uds_validate_props`, and the UI picker should offer, so the model emits `color="warning"`.
|
|
206
|
+
*/
|
|
207
|
+
function stylePropValueLeaves(config, path) {
|
|
208
|
+
return stylePropEntries(config, path).map((entry) => entry.leaf);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Resolve a style-prop leaf value back to its qualified token path (or literal) — the inverse of
|
|
212
|
+
* {@link stylePropValueLeaves}, for validating + rendering an authored `bg="subtle"`. `undefined`
|
|
213
|
+
* when the leaf isn't in the prop's domain.
|
|
214
|
+
*/
|
|
215
|
+
function stylePropValueFromLeaf(config, path, leaf) {
|
|
216
|
+
return stylePropEntries(config, path).find((entry) => entry.leaf === leaf)?.value;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* The token a style-prop leaf binds, as a qualified path — `('bg', 'brand')` → `color/brand`.
|
|
220
|
+
* `undefined` when the leaf is a keyword the system declared rather than a token (`row`,
|
|
221
|
+
* `flex-start`), or isn't in the prop's domain at all.
|
|
222
|
+
*
|
|
223
|
+
* The pair below is what lets a bag store the binding as a ref while an author keeps writing the
|
|
224
|
+
* leaf: this direction mints the ref at parse time, {@link stylePropLeafForToken} reads it back for
|
|
225
|
+
* the class name. Narrowed to values that actually resolve as tokens, so a keyword is left as the
|
|
226
|
+
* string it is — a ref to a token that doesn't exist would be a dangling edge, which is worse than
|
|
227
|
+
* the missing one it replaced.
|
|
228
|
+
*/
|
|
229
|
+
function stylePropTokenPath(config, path, leaf) {
|
|
230
|
+
const value = stylePropValueFromLeaf(config, path, leaf);
|
|
231
|
+
if (value === void 0) return void 0;
|
|
232
|
+
return config.resolveRef(ref("token", value)) ? value : void 0;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The leaf a style prop calls a token — `('bg', 'color/brand')` → `brand`. The read twin of
|
|
236
|
+
* {@link stylePropTokenPath}, and the reason storing the ref costs no reader its class name.
|
|
237
|
+
*
|
|
238
|
+
* Not `refLeaf`: that strips one path segment, which is right for a variant value
|
|
239
|
+
* (`value:variant/solid`) and wrong here, because a prop's token group can be several segments deep
|
|
240
|
+
* (`spectrum/blue/1` under group `spectrum/blue` is the leaf `1`, not `blue/1`). The group is a fact
|
|
241
|
+
* about the prop's domain, so only the domain can say where the leaf starts.
|
|
242
|
+
*
|
|
243
|
+
* First match wins, the same way {@link stylePropValueFromLeaf} resolves the other direction — a
|
|
244
|
+
* prop binding two groups that share a value already lets authored order decide.
|
|
245
|
+
*/
|
|
246
|
+
function stylePropLeafForToken(config, path, tokenPath) {
|
|
247
|
+
return stylePropEntries(config, path).find((entry) => entry.value === tokenPath)?.leaf;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* The leaf an author wrote for a stored ref in a bag — the read side of the binding
|
|
251
|
+
* `value-domain.ts` performs on the way in.
|
|
252
|
+
*
|
|
253
|
+
* Two cases, because where the leaf starts differs:
|
|
254
|
+
*
|
|
255
|
+
* - a token is reached through a style property, whose domain decides how much of the path is the
|
|
256
|
+
* group. `spectrum/blue/1` under the group `spectrum/blue` is the leaf `1`, so the property's own
|
|
257
|
+
* domain is the only thing that can say — hence the lookup rather than trimming a segment.
|
|
258
|
+
* - anything else (a composite member, a motion preset) is named under the group its prop points at,
|
|
259
|
+
* and the authored name is the last segment. This holds for a nested group too: `overlays/elev/1`
|
|
260
|
+
* under `overlays/elev` is `1`.
|
|
261
|
+
*
|
|
262
|
+
* `undefined` when the ref isn't one a bag value could have been authored as, so the caller leaves it
|
|
263
|
+
* alone rather than inventing a name for it.
|
|
264
|
+
*/
|
|
265
|
+
function authoredValue(config, key, value) {
|
|
266
|
+
if (!isRef(value)) return value;
|
|
267
|
+
return authoredLeaf(config, key, value.__ref) ?? value;
|
|
268
|
+
}
|
|
269
|
+
function authoredLeaf(config, key, stored) {
|
|
270
|
+
const path = pathOf(stored);
|
|
271
|
+
if (kindOf(stored) === "token") return stylePropLeafForToken(config, key, path);
|
|
272
|
+
const slash = path.indexOf("/");
|
|
273
|
+
return slash === -1 ? path : path.slice(slash + 1);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* A style bag as a surface reads it — every token binding read back to the leaf an author wrote.
|
|
277
|
+
*
|
|
278
|
+
* The inverse of the parse-time binding (`bindBagTokens` on `Component`), and the one place that
|
|
279
|
+
* inverse lives: a bag is stored with refs so the dependency graph sees the edges, while every
|
|
280
|
+
* consumer that renders, edits, or bakes one wants the leaf. Two implementations of this would be two
|
|
281
|
+
* chances to disagree about what a value is called — and the panel and the native theme disagreeing
|
|
282
|
+
* about that is exactly the class of bug the single description is meant to remove.
|
|
283
|
+
*
|
|
284
|
+
* Recurses into modifier blocks, since their values are values too. Returns the bag it was given when
|
|
285
|
+
* nothing was bound, so an untouched bag keeps its identity.
|
|
286
|
+
*/
|
|
287
|
+
function authoredBag(config, bag) {
|
|
288
|
+
const out = {};
|
|
289
|
+
let changed = false;
|
|
290
|
+
for (const [key, value] of Object.entries(bag)) {
|
|
291
|
+
if (isRef(value)) {
|
|
292
|
+
const leaf = authoredLeaf(config, key, value.__ref);
|
|
293
|
+
if (leaf !== void 0) {
|
|
294
|
+
out[key] = leaf;
|
|
295
|
+
changed = true;
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (value && typeof value === "object" && !Array.isArray(value) && !isRef(value)) {
|
|
300
|
+
const inner = authoredBag(config, value);
|
|
301
|
+
out[key] = inner;
|
|
302
|
+
changed ||= inner !== value;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
out[key] = value;
|
|
306
|
+
}
|
|
307
|
+
return changed ? out : bag;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* The style property a prop on `elementType` writes through.
|
|
311
|
+
*
|
|
312
|
+
* Two questions get asked of a config constantly — *what CSS does this prop on this element write?*
|
|
313
|
+
* and *which prop writes this CSS?* — and both have to be answered from the config rather than from a
|
|
314
|
+
* prop's name: `spacingX` in one system is `spacingHorizontal` in the next, so CSS is the stable
|
|
315
|
+
* contract between them.
|
|
316
|
+
*
|
|
317
|
+
* One `StyleProperty` read on top of {@link stylePropertyPathFor}, which is where the routing lives:
|
|
318
|
+
* the component's own table where it declares the prop, the name where it doesn't — which is what an
|
|
319
|
+
* element styled through a raw tag needs, since a raw tag declares no props at all.
|
|
320
|
+
*
|
|
321
|
+
* `elementType` may arrive as a spec element's stored marker (`component:Media`) or as a bare path;
|
|
322
|
+
* the marker is stripped, because the component lookup keys on the path. Missing that strip is
|
|
323
|
+
* invisible in a system whose prop names are its style-property paths — the fallback answers
|
|
324
|
+
* correctly — and wrong the moment a component names a prop something else.
|
|
325
|
+
*/
|
|
326
|
+
function stylePropertyFor(config, elementType, propName) {
|
|
327
|
+
const writes = stylePropertyPathFor({
|
|
328
|
+
config,
|
|
329
|
+
through: [elementType],
|
|
330
|
+
key: propName
|
|
331
|
+
});
|
|
332
|
+
return writes === void 0 ? void 0 : StyleProperty.get(config, writes);
|
|
333
|
+
}
|
|
334
|
+
/** Every style property that writes `cssProperty`, in registration order. */
|
|
335
|
+
function stylePropertiesWriting(config, cssProperty) {
|
|
336
|
+
return StyleProperty.list(config).filter((prop) => prop.properties.includes(cssProperty));
|
|
337
|
+
}
|
|
338
|
+
/** Whether a style property accepts `value` — i.e. the value is one of its leaves. */
|
|
339
|
+
function stylePropertyAccepts(config, prop, value) {
|
|
340
|
+
return stylePropEntries(config, prop.path).some((entry) => entry.leaf === value);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Each token-backed value a style prop offers, as `leaf → resolved literal` (`'2' → '0.5rem'`) — what
|
|
344
|
+
* a token picker shows beside each name. Keyword values are absent: there's no token behind them, so
|
|
345
|
+
* there's nothing to resolve.
|
|
346
|
+
*/
|
|
347
|
+
function stylePropTokenValues(config, propName) {
|
|
348
|
+
const map = {};
|
|
349
|
+
for (const entry of stylePropEntries(config, propName)) {
|
|
350
|
+
const literal = resolveTokenValue(config, entry.value);
|
|
351
|
+
if (literal !== void 0) map[entry.leaf] = String(literal);
|
|
352
|
+
}
|
|
353
|
+
return map;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* The token group a style prop draws its values from (`spacing`) — what an "open this in the system"
|
|
357
|
+
* link needs. `undefined` when the prop's values are keywords rather than tokens.
|
|
358
|
+
*
|
|
359
|
+
* Derived from the values themselves rather than from a declared group list: a token-backed value
|
|
360
|
+
* resolves to its qualified path, and everything before the leaf is the group it lives in.
|
|
361
|
+
*/
|
|
362
|
+
function stylePropTokenGroup(config, propName) {
|
|
363
|
+
for (const entry of stylePropEntries(config, propName)) {
|
|
364
|
+
const slash = entry.value.lastIndexOf("/");
|
|
365
|
+
if (slash > 0 && config.resolve("token", entry.value)) return entry.value.slice(0, slash);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* The value of whichever prop in `props` writes `cssProperty`.
|
|
370
|
+
*
|
|
371
|
+
* Reads the element's own props rather than the styling the config would apply, because a caller
|
|
372
|
+
* asking this is asking about a choice someone made on this element (pinning a position, locking an
|
|
373
|
+
* aspect ratio) — not about what the component's rules paint by default.
|
|
374
|
+
*/
|
|
375
|
+
function cssPropValue(config, elementType, props, cssProperty) {
|
|
376
|
+
for (const [name, value] of Object.entries(props)) {
|
|
377
|
+
if (typeof value !== "string") continue;
|
|
378
|
+
if (stylePropertyFor(config, elementType, name)?.properties.includes(cssProperty)) return value;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* A per-config tag for cycle keys, so one chase can distinguish two configs that spell a path the
|
|
383
|
+
* same way. Assigned lazily and held weakly — a `Config` is a value, so this keys on the exact
|
|
384
|
+
* instance being walked and dies with it.
|
|
385
|
+
*/
|
|
386
|
+
const chaseTags = /* @__PURE__ */ new WeakMap();
|
|
387
|
+
let nextChaseTag = 0;
|
|
388
|
+
function chaseTag(config) {
|
|
389
|
+
let tag = chaseTags.get(config);
|
|
390
|
+
if (tag === void 0) {
|
|
391
|
+
tag = String(nextChaseTag++);
|
|
392
|
+
chaseTags.set(config, tag);
|
|
393
|
+
}
|
|
394
|
+
return tag;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* One field of one entity, with refs chased to the literal they denote — local and linked alike.
|
|
398
|
+
*
|
|
399
|
+
* The whole point is that there is no second walk for a borrow. A ref is a ref: `isRef` is the only
|
|
400
|
+
* branch, and whether it carries a `@source` decides only which config the next hop reads, never
|
|
401
|
+
* whether a hop happens. That is what makes "reference something in my system" and "reference
|
|
402
|
+
* something from a linked system" the same feature rather than two that have to agree.
|
|
403
|
+
*
|
|
404
|
+
* A ref stands in for the same field of the entity it names — a token's `value` aliasing another
|
|
405
|
+
* Token's `value`, a composite's `styles` standing in for another composite's `styles` — so the field
|
|
406
|
+
* carries through the chase rather than each kind needing to declare a target.
|
|
407
|
+
*
|
|
408
|
+
* Answers with whatever the ref denotes rather than narrowing to a scalar — a borrowed gradient or
|
|
409
|
+
* derived colour is a resolved value too, and a chase that returned `undefined` for one would be
|
|
410
|
+
* indistinguishable from a chase that failed. A caller wanting a literal narrows.
|
|
411
|
+
*
|
|
412
|
+
* `undefined` only when there is no answer: a missing entity, an unreachable linked source, or a cycle.
|
|
413
|
+
*/
|
|
414
|
+
function resolveFieldValue(config, kind, path, field, seen = /* @__PURE__ */ new Set()) {
|
|
415
|
+
const entity = config.resolve(kind, path);
|
|
416
|
+
if (!entity) return void 0;
|
|
417
|
+
const value = entity.toJSON()[field];
|
|
418
|
+
if (isRef(value)) {
|
|
419
|
+
const key = `${chaseTag(config)}|${value.__ref}`;
|
|
420
|
+
if (seen.has(key)) return void 0;
|
|
421
|
+
seen.add(key);
|
|
422
|
+
const source = config.configFor(value);
|
|
423
|
+
if (source) return resolveFieldValue(source, kindOf(value), pathOf(value), field, seen);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
return value;
|
|
427
|
+
}
|
|
428
|
+
/** A token's value with alias refs chased to the base literal — {@link resolveFieldValue} for the one
|
|
429
|
+
* field every token consumer asks about, narrowed to the scalar a display or a stylesheet can use. */
|
|
430
|
+
function resolveTokenValue(config, path, seen = /* @__PURE__ */ new Set()) {
|
|
431
|
+
const value = resolveFieldValue(config, "token", path, "value", seen);
|
|
432
|
+
return typeof value === "string" || typeof value === "number" ? value : void 0;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* The literal a token resolves to under an override condition: its own override there when it holds
|
|
436
|
+
* one, else its base value, with a ref along the way chased under the same condition. That is how the
|
|
437
|
+
* cascade reads it — under `dark`, an alias of `blue/1` is blue/1's dark value — so a swatch painted
|
|
438
|
+
* for a dark cell paints what dark will show. `null` is the base, the same as {@link resolveTokenValue}.
|
|
439
|
+
*/
|
|
440
|
+
function resolveTokenValueUnder(config, path, condition, seen = /* @__PURE__ */ new Set()) {
|
|
441
|
+
if (condition === null) return resolveTokenValue(config, path, seen);
|
|
442
|
+
const entity = config.resolve("token", path);
|
|
443
|
+
if (!entity) return void 0;
|
|
444
|
+
const body = entity.toJSON();
|
|
445
|
+
const own = body.overrides?.[condition];
|
|
446
|
+
const value = own !== void 0 ? own.value : body.value;
|
|
447
|
+
if (isRef(value)) {
|
|
448
|
+
const key = `${chaseTag(config)}|${value.__ref}`;
|
|
449
|
+
if (seen.has(key)) return void 0;
|
|
450
|
+
seen.add(key);
|
|
451
|
+
const source = config.configFor(value);
|
|
452
|
+
if (!source || kindOf(value) !== "token") return void 0;
|
|
453
|
+
return resolveTokenValueUnder(source, pathOf(value), condition, seen);
|
|
454
|
+
}
|
|
455
|
+
return typeof value === "string" || typeof value === "number" ? value : void 0;
|
|
456
|
+
}
|
|
457
|
+
/** A style-bag value → its CSS string: a token ref (or a bare token path) becomes a
|
|
458
|
+
* `var(--…)` reference (so mode/theme overrides still flow), anything else is the literal.
|
|
459
|
+
*
|
|
460
|
+
* `varPrefix` renames the namespace every variable in the result is spelled under, and is how a
|
|
461
|
+
* linked source's own values are rendered into the consumer's stylesheet: their token aliasing
|
|
462
|
+
* another of theirs has to name the variable as we declared it, not as they would have. Absent — the
|
|
463
|
+
* ordinary call — it is `config`'s own prefix. */
|
|
464
|
+
function renderStyleValue(config, value, varPrefix) {
|
|
465
|
+
if (Array.isArray(value)) return value.map((part) => renderStyleValue(config, part, varPrefix)).join(" ");
|
|
466
|
+
if (isDerivedColor(value)) return renderDerivedColor(value, (color) => renderStyleValue(config, color, varPrefix));
|
|
467
|
+
if (isGradient(value)) return renderGradient(value, (color) => renderStyleValue(config, color, varPrefix));
|
|
468
|
+
if (isRef(value)) {
|
|
469
|
+
const source = config.configFor(value);
|
|
470
|
+
if (!source) return "";
|
|
471
|
+
const slug = sourceOf(value);
|
|
472
|
+
return cssVarRef(source, "token", pathOf(value), slug === void 0 ? varPrefix : sourceVarPrefix(config, slug));
|
|
473
|
+
}
|
|
474
|
+
if (typeof value === "string" && sourceOf(value) !== void 0) {
|
|
475
|
+
const asRef = ref("token", value);
|
|
476
|
+
if (config.resolveRef(asRef)) return renderStyleValue(config, asRef, varPrefix);
|
|
477
|
+
}
|
|
478
|
+
if (typeof value === "string" && config.resolve("token", value)) return cssVarRef(config, "token", value, varPrefix);
|
|
479
|
+
if (typeof value === "string" && value.startsWith("[") && value.endsWith("]")) return value.slice(1, -1).replace(/url\([^)]*\)|_/g, (span) => span === "_" ? " " : span);
|
|
480
|
+
return String(value);
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* The value a negating prop paints — `calc(<value> * -1)`.
|
|
484
|
+
*
|
|
485
|
+
* `calc` rather than a computed literal because the value is usually a `var(--…)`: the token it names
|
|
486
|
+
* has to stay a reference, so a mode override or a theme swap still reaches it. It's applied at the
|
|
487
|
+
* declaration, not to the token, since the same token feeds the positive prop too.
|
|
488
|
+
*
|
|
489
|
+
* Exported because three emitters need the same string — the stylesheet, the runtime's off-scale
|
|
490
|
+
* branch, and this file's `styleDeclarations` — and a prop that negates from a token but not from a
|
|
491
|
+
* literal is worse than one that never negates at all.
|
|
492
|
+
*/
|
|
493
|
+
function negatedCssValue(value) {
|
|
494
|
+
return `calc(${value} * -1)`;
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* What one component's own module does with each prop it declares — name → the style property that
|
|
498
|
+
* prop writes, or `null` for a prop that writes none.
|
|
499
|
+
*
|
|
500
|
+
* This is the routing an emitted module performs, and it is a different question from
|
|
501
|
+
* {@link resolveComponentProps} / `prop-surface`, which answer what a component exposes by chasing
|
|
502
|
+
* `forwards` and `inherits`. Both are right for their callers — an inspector offers the exposed
|
|
503
|
+
* surface — but only this one describes what happens at runtime, because a module destructures its
|
|
504
|
+
* declared props out of the bag and hands the rest to `processStyleProps`:
|
|
505
|
+
*
|
|
506
|
+
* const { className, layerProps, variant, size = "md", children, ...__udsBag } = jsxProps;
|
|
507
|
+
* const [classes, rest] = processStyleProps(__udsBag, { gap: "gapY" });
|
|
508
|
+
*
|
|
509
|
+
* So a declared prop is answered by its declaration whatever its type, and a prop the component does
|
|
510
|
+
* not declare falls through by its own name to the root layer — where every prop the config doesn't
|
|
511
|
+
* model is sent (`rootLayerOf`).
|
|
512
|
+
*
|
|
513
|
+
* Three consequences, each a silent miss for a caller that reads the exposed surface instead:
|
|
514
|
+
*
|
|
515
|
+
* - **A rename is the component's own.** `TokenCellShell` forwards `*` from `HStack`, so its exposed
|
|
516
|
+
* surface reports `gap` as `gapX` — but its module carries no alias, consumes `gap` under the
|
|
517
|
+
* System-wide name, and its baked rule emits `gap:` to match. A forwarded rename is not inherited.
|
|
518
|
+
* - **A `variant`, `composite`, `slot`, `forward`, `string`, `number` or `boolean` prop writes no
|
|
519
|
+
* style property at all**, even where a style property shares its name. It is destructured out
|
|
520
|
+
* before the style-prop table is ever consulted.
|
|
521
|
+
* - **A `null` tombstone is a declaration too** — the prop is declared gone, not merely absent, and
|
|
522
|
+
* it is destructured out like any other. It writes nothing and does not fall through.
|
|
523
|
+
*/
|
|
524
|
+
function declaredPropRouting(config, path) {
|
|
525
|
+
const { props = {} } = config.resolve("component", path)?.toJSON() ?? {};
|
|
526
|
+
const routing = /* @__PURE__ */ new Map();
|
|
527
|
+
for (const [name, declaration] of Object.entries(props)) {
|
|
528
|
+
const writes = declaration?.type === "styleProperty" && isRef(declaration.value) ? pathOf(declaration.value.__ref) : null;
|
|
529
|
+
routing.set(name, writes);
|
|
530
|
+
}
|
|
531
|
+
return routing;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* The renames out of {@link declaredPropRouting} — the table a component's module is emitted with and
|
|
535
|
+
* hands to `processStyleProps`.
|
|
536
|
+
*
|
|
537
|
+
* Only the renames, because a prop named after its own property is what the runtime assumes when it
|
|
538
|
+
* finds no entry (`aliases?.[key] ?? key`). So this map is the difference between a prop's
|
|
539
|
+
* consumer-facing name and what it routes to, and an empty answer means "they are the same".
|
|
540
|
+
*/
|
|
541
|
+
function styleAliasesOf(config, path) {
|
|
542
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
543
|
+
for (const [name, writes] of declaredPropRouting(config, path)) if (writes !== null && writes !== name) aliases.set(name, writes);
|
|
544
|
+
return aliases;
|
|
545
|
+
}
|
|
546
|
+
/** Whether a style prop paints the negation of its value (`offsetX`). */
|
|
547
|
+
function stylePropNegates(config, path) {
|
|
548
|
+
return (config.resolve("style-property", path)?.toJSON())?.negates === true;
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Flatten a style bag to resolved CSS declarations — each key resolves to its style prop's
|
|
552
|
+
* CSS `property` (falling back to the key itself as a raw property), each value renders as a
|
|
553
|
+
* Token ref or a literal. `_`-prefixed keys are modifier blocks the caller nests, so they're
|
|
554
|
+
* skipped. The single style→CSS resolver shared by codegen (emit CSS text) and the renderer
|
|
555
|
+
* (inline styles) so a rendered preview matches the emitted stylesheet exactly.
|
|
556
|
+
*/
|
|
557
|
+
/**
|
|
558
|
+
* The style property one bag key writes — the single answer to "what does this prop mean here",
|
|
559
|
+
* for every surface that reads a prop bag.
|
|
560
|
+
*
|
|
561
|
+
* A bag key is an authored name, and what it writes is settled by the definitions it resolves
|
|
562
|
+
* against, in `through` order — first one that declares the key wins, and its answer is the whole
|
|
563
|
+
* answer. A key nothing declares names its own property, which is what a raw tag needs, since a raw
|
|
564
|
+
* tag declares no props at all.
|
|
565
|
+
*
|
|
566
|
+
* `through` is a chain rather than one element because a style rule's bag has two definitions with a
|
|
567
|
+
* claim on it: the component that authored the rule, and the element whose props the rule sets.
|
|
568
|
+
* The author comes first — a component's own declaration of `gap` is what `gap` means throughout its
|
|
569
|
+
* definition, its public API and its style rules alike. So `Row`, which declares `gap` writing
|
|
570
|
+
* `gapX` over a `VStack` layer that writes `gapY`, means column-gap in both places rather than one
|
|
571
|
+
* in each.
|
|
572
|
+
*
|
|
573
|
+
* A member ref key (`component:layout/HStack#props/gap`) names its element outright, so it answers
|
|
574
|
+
* for itself and `through` is irrelevant.
|
|
575
|
+
*
|
|
576
|
+
* `undefined` means "writes no style property": a `variant`, a `composite`, a tombstone, or a key
|
|
577
|
+
* that is not a style property at all (`as`, `type`, a data attribute). Every caller already had to
|
|
578
|
+
* distinguish that from a property, and each one used to decide it differently — the CSS emitter by
|
|
579
|
+
* `config.resolve('style-property', key)` on the authored name, which both mis-resolved a rename and
|
|
580
|
+
* silently dropped a key whose only meaning is a rename (`colSpan`, not itself a property, emitted no
|
|
581
|
+
* declaration at all).
|
|
582
|
+
*/
|
|
583
|
+
function stylePropertyPathFor({ config, through = [], key }) {
|
|
584
|
+
const member = memberOf(key);
|
|
585
|
+
if (member) {
|
|
586
|
+
const name = member.slice(member.indexOf("/") + 1);
|
|
587
|
+
const writes = declaredPropRouting(config, pathOf(key)).get(name);
|
|
588
|
+
return writes === void 0 ? nameAsProperty(config, name) : writes ?? void 0;
|
|
589
|
+
}
|
|
590
|
+
for (const definition of through) {
|
|
591
|
+
if (definition === void 0) continue;
|
|
592
|
+
const writes = declaredPropRouting(config, pathOf(definition)).get(key);
|
|
593
|
+
if (writes !== void 0) return writes ?? void 0;
|
|
594
|
+
}
|
|
595
|
+
return nameAsProperty(config, key);
|
|
596
|
+
}
|
|
597
|
+
/** A key read as a style-property path — `undefined` when no such property exists. */
|
|
598
|
+
function nameAsProperty(config, key) {
|
|
599
|
+
return config.resolve("style-property", key) ? key : void 0;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* A bag with every key rewritten to the property it writes, resolved through the definitions that
|
|
603
|
+
* have a claim on it ({@link stylePropertyPathFor}). The one place a bag's authored names become
|
|
604
|
+
* properties.
|
|
605
|
+
*
|
|
606
|
+
* Applied at each boundary rather than per key inside each consumer, so `styleDeclarations`, the CSS
|
|
607
|
+
* emitter and `splitStyleProps` all stay pure functions of a property-keyed bag and cannot disagree
|
|
608
|
+
* about what a key meant. A key that writes no style property is left alone, under the name it was
|
|
609
|
+
* authored with: it is the element's own (`as`, `type`, `onClick`), and the consumer routes it there.
|
|
610
|
+
*
|
|
611
|
+
* `_<modifier>` blocks hold an ordinary bag against the same definitions, so they recurse.
|
|
612
|
+
*/
|
|
613
|
+
function routedBag({ config, through, bag }) {
|
|
614
|
+
const routed = {};
|
|
615
|
+
for (const [key, value] of Object.entries(bag)) {
|
|
616
|
+
if (key.startsWith("_") && value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
617
|
+
routed[key] = routedBag({
|
|
618
|
+
config,
|
|
619
|
+
through,
|
|
620
|
+
bag: value
|
|
621
|
+
});
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
const writes = stylePropertyPathFor({
|
|
625
|
+
config,
|
|
626
|
+
through,
|
|
627
|
+
key
|
|
628
|
+
});
|
|
629
|
+
routed[writes ?? key] = value;
|
|
630
|
+
}
|
|
631
|
+
return routed;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* The leaf a bag value names, for looking its entry up — or `undefined` when it names none.
|
|
635
|
+
*
|
|
636
|
+
* A flag is written bare (`truncate: true`) and its entry sits under the empty leaf, which is the same
|
|
637
|
+
* entry the class `.uds-truncate` is built from. `false` is the prop's absence and has no entry.
|
|
638
|
+
*/
|
|
639
|
+
function leafAuthoredFor(value) {
|
|
640
|
+
if (typeof value === "boolean") return value ? "" : void 0;
|
|
641
|
+
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
642
|
+
}
|
|
643
|
+
function styleDeclarations({ config, bag, namespace }) {
|
|
644
|
+
const out = {};
|
|
645
|
+
const fades = /* @__PURE__ */ new Map();
|
|
646
|
+
for (const [rawKey, value] of Object.entries(bag)) {
|
|
647
|
+
if (rawKey.startsWith("_") || rawKey.startsWith("--")) continue;
|
|
648
|
+
const key = stylePropertyPathFor({
|
|
649
|
+
config,
|
|
650
|
+
key: rawKey
|
|
651
|
+
}) ?? rawKey;
|
|
652
|
+
const target = (config.resolve("style-property", key)?.toJSON())?.opacityOf;
|
|
653
|
+
if (!isRef(target)) continue;
|
|
654
|
+
const rendered = renderBagValue({
|
|
655
|
+
config,
|
|
656
|
+
key,
|
|
657
|
+
value,
|
|
658
|
+
namespace
|
|
659
|
+
});
|
|
660
|
+
if (rendered === void 0) continue;
|
|
661
|
+
const amount = opacityPercentage(rendered);
|
|
662
|
+
if (amount !== void 0) fades.set(pathOf(target), amount);
|
|
663
|
+
}
|
|
664
|
+
for (const [rawKey, value] of Object.entries(bag)) {
|
|
665
|
+
if (rawKey.startsWith("_")) continue;
|
|
666
|
+
if (rawKey.startsWith("--")) {
|
|
667
|
+
out[rawKey] = renderStyleValue(config, value, namespace);
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
const key = stylePropertyPathFor({
|
|
671
|
+
config,
|
|
672
|
+
key: rawKey
|
|
673
|
+
}) ?? rawKey;
|
|
674
|
+
const prop = config.resolve("style-property", key);
|
|
675
|
+
if (!prop) continue;
|
|
676
|
+
const body = prop.toJSON();
|
|
677
|
+
if (isRef(body.opacityOf)) continue;
|
|
678
|
+
const cssProperties = Array.isArray(body.properties) ? body.properties : [];
|
|
679
|
+
const authoredLeafValue = leafAuthoredFor(value);
|
|
680
|
+
const entry = authoredLeafValue === void 0 ? void 0 : stylePropEntries(config, key).find((candidate) => candidate.leaf === authoredLeafValue);
|
|
681
|
+
if (entry?.declarations) {
|
|
682
|
+
for (const [cssProperty, declaredValue] of Object.entries(entry.declarations)) out[cssProperty] = renderStyleValue(config, declaredValue, namespace);
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
const base = renderBagValue({
|
|
686
|
+
config,
|
|
687
|
+
key,
|
|
688
|
+
value,
|
|
689
|
+
namespace
|
|
690
|
+
});
|
|
691
|
+
if (base === void 0) continue;
|
|
692
|
+
let rendered = stylePropNegates(config, key) ? negatedCssValue(base) : base;
|
|
693
|
+
const fade = fades.get(key);
|
|
694
|
+
if (fade !== void 0) rendered = `color-mix(in srgb, ${rendered} ${fade}, transparent)`;
|
|
695
|
+
for (const cssProperty of cssProperties) out[cssProperty] = rendered;
|
|
696
|
+
}
|
|
697
|
+
return out;
|
|
698
|
+
}
|
|
699
|
+
/** One bag value as CSS text, or `undefined` for a flag turned off. */
|
|
700
|
+
function renderBagValue({ config, key, value, namespace }) {
|
|
701
|
+
let resolved = value;
|
|
702
|
+
if (typeof value === "string") {
|
|
703
|
+
const qualified = stylePropValueFromLeaf(config, key, value);
|
|
704
|
+
if (qualified !== void 0) resolved = qualified;
|
|
705
|
+
}
|
|
706
|
+
if (typeof value === "boolean") {
|
|
707
|
+
if (!value) return void 0;
|
|
708
|
+
const bare = stylePropValueFromLeaf(config, key, "");
|
|
709
|
+
if (bare === void 0) return void 0;
|
|
710
|
+
resolved = bare;
|
|
711
|
+
}
|
|
712
|
+
return renderStyleValue(config, resolved, namespace);
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* An opacity value as a `color-mix()` share.
|
|
716
|
+
*
|
|
717
|
+
* A system may scale opacity as a fraction (`0.5`) or as a percentage (`50%`), and `color-mix()`
|
|
718
|
+
* takes only the latter — a bare `0.5` there is not a small share but a syntax error, and the
|
|
719
|
+
* browser drops the whole declaration.
|
|
720
|
+
*
|
|
721
|
+
* A token reference is the common case and cannot be read here: the value is a `var()`, and what it
|
|
722
|
+
* holds is the theme's answer, which changes per mode. So it is scaled in CSS (`calc(… * 100%)`)
|
|
723
|
+
* rather than resolved, which commits the config to authoring opacity tokens as fractions — the same
|
|
724
|
+
* convention CSS's own `opacity` property uses.
|
|
725
|
+
*/
|
|
726
|
+
function opacityPercentage(value) {
|
|
727
|
+
if (value.includes("var(")) return `calc(${value} * 100%)`;
|
|
728
|
+
if (value.trim().endsWith("%")) return value.trim();
|
|
729
|
+
const numeric = Number(value);
|
|
730
|
+
if (!Number.isFinite(numeric)) return void 0;
|
|
731
|
+
return numeric <= 1 ? `${numeric * 100}%` : `${numeric}%`;
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* A component's effective props — its own props merged over those it inherits by `extends`ing
|
|
735
|
+
* another component. A child prop overrides the inherited one of the same name; a `null` child
|
|
736
|
+
* prop omits an inherited one. A native-tag `extends` (a string, not a ref) inherits nothing,
|
|
737
|
+
* and a composite doesn't extend, so both resolve to just their own props. Chases the `extends`
|
|
738
|
+
* chain (transitive inheritance), cycle-guarded.
|
|
739
|
+
*/
|
|
740
|
+
function resolveComponentProps(config, path, seen = /* @__PURE__ */ new Set()) {
|
|
741
|
+
if (seen.has(path)) return {};
|
|
742
|
+
seen.add(path);
|
|
743
|
+
const comp = config.resolve("component", path);
|
|
744
|
+
if (!comp) return {};
|
|
745
|
+
const body = comp.toJSON();
|
|
746
|
+
const merged = {};
|
|
747
|
+
for (const [layer, entry] of Object.entries(forwardsOf(body))) {
|
|
748
|
+
const element = body.layers?.[layer];
|
|
749
|
+
if (!isRef(element)) continue;
|
|
750
|
+
const exposed = resolveComponentProps(config, pathOf(element.__ref), new Set(seen));
|
|
751
|
+
const taken = entry === "*" ? exposed : Object.fromEntries((Array.isArray(entry) ? entry : []).map((r) => isRef(r) ? memberOf(r)?.split("/").pop() : void 0).filter((name) => !!name && name in exposed).map((name) => [name, exposed[name]]));
|
|
752
|
+
for (const [name, decl] of Object.entries(taken)) if (!(name in merged)) merged[name] = decl;
|
|
753
|
+
}
|
|
754
|
+
for (const [name, prop] of Object.entries(body.props ?? {})) if (prop === null) delete merged[name];
|
|
755
|
+
else merged[name] = prop;
|
|
756
|
+
return merged;
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Which component actually declared one of a component's resolved props — its ultimate owner.
|
|
760
|
+
*
|
|
761
|
+
* `resolveComponentProps` answers *what* the props are and deliberately flattens away *where each came
|
|
762
|
+
* from*. That provenance is a real question with more than one caller: a rename recorded against one
|
|
763
|
+
* Component has to reach the documents of every component that exposes that prop through `inherits`
|
|
764
|
+
* (`rewriteSpecRefs`), and telling an author which side of a merge a prop came from needs the same fact.
|
|
765
|
+
*
|
|
766
|
+
* Mirrors the merge exactly, so the two can't disagree:
|
|
767
|
+
*
|
|
768
|
+
* - an own non-null declaration means this component owns it — a shadow stops the walk, which is why
|
|
769
|
+
* `HStack`'s own `gap` is `HStack`'s and not the `Box` `gap` it shadows;
|
|
770
|
+
* - an own `null` is the omit tombstone, so the prop does not exist here and has no owner;
|
|
771
|
+
* - otherwise the prop is inherited, and the answer is whatever the inherited component says —
|
|
772
|
+
* transitively, so `Button` → `Pressable` → `Box` reports `Box`.
|
|
773
|
+
*
|
|
774
|
+
* `undefined` when the component doesn't resolve, doesn't expose that prop, or tombstones it.
|
|
775
|
+
*/
|
|
776
|
+
function propOwner(config, path, prop, seen = /* @__PURE__ */ new Set()) {
|
|
777
|
+
if (seen.has(path)) return void 0;
|
|
778
|
+
seen.add(path);
|
|
779
|
+
const comp = config.resolve("component", path);
|
|
780
|
+
if (!comp) return void 0;
|
|
781
|
+
const body = comp.toJSON();
|
|
782
|
+
const own = body.props ?? {};
|
|
783
|
+
if (prop in own) return own[prop] === null ? void 0 : path;
|
|
784
|
+
for (const [layer, entry] of Object.entries(forwardsOf(body))) {
|
|
785
|
+
const element = body.layers?.[layer];
|
|
786
|
+
if (!isRef(element)) continue;
|
|
787
|
+
if (Array.isArray(entry) && !entry.some((r) => isRef(r) && memberOf(r)?.split("/").pop() === prop)) continue;
|
|
788
|
+
const owner = propOwner(config, pathOf(element.__ref), prop, new Set(seen));
|
|
789
|
+
if (owner) return owner;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
/** A prop declaration's accepted value domain — the leaves an author may write (`warning`,
|
|
793
|
+
* `display2`, a variant name), plus an `accepts` check. Resolved once, relationally, from the prop's
|
|
794
|
+
* `type`: a `variant`'s own values; a `styleProperty`'s referenced value-set leaves; a `composite` group's
|
|
795
|
+
* member leaves. `null` when there's no enumerable domain (a slot/motion content prop, a free scalar,
|
|
796
|
+
* or an unconstrained style prop). The one place prop-value domains are derived — consumed by prop
|
|
797
|
+
* validation, the `get_component` prop-domain display, and the Studio value picker, so none re-derive. */
|
|
798
|
+
function propValueDomain(config, decl) {
|
|
799
|
+
const domainRef = () => decl.value?.__ref;
|
|
800
|
+
if (decl.type === "variant") {
|
|
801
|
+
const target = domainRef();
|
|
802
|
+
if (target) {
|
|
803
|
+
const leaves = memberLeaves(config, target);
|
|
804
|
+
if (leaves.length === 0) return null;
|
|
805
|
+
return {
|
|
806
|
+
leaves,
|
|
807
|
+
accepts: (value) => leaves.includes(value)
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
const values = listedIn(decl.value) ?? [];
|
|
811
|
+
return {
|
|
812
|
+
leaves: values,
|
|
813
|
+
accepts: (v) => values.includes(v)
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
if (decl.type === "styleProperty") {
|
|
817
|
+
const target = domainRef();
|
|
818
|
+
if (!target) return null;
|
|
819
|
+
const path = splitRef(target).qualifiedPath;
|
|
820
|
+
const leaves = stylePropValueLeaves(config, path);
|
|
821
|
+
if (leaves.length === 0) return null;
|
|
822
|
+
return {
|
|
823
|
+
leaves,
|
|
824
|
+
accepts: (v) => stylePropValueFromLeaf(config, path, v) !== void 0
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
if (decl.type === "composite") {
|
|
828
|
+
const target = domainRef();
|
|
829
|
+
if (!target) return null;
|
|
830
|
+
const leaves = memberLeaves(config, target);
|
|
831
|
+
if (leaves.length === 0) return null;
|
|
832
|
+
return {
|
|
833
|
+
leaves,
|
|
834
|
+
accepts: (v) => memberFromLeaf(config, target, v) !== void 0
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
return null;
|
|
838
|
+
}
|
|
839
|
+
/** Validate a prop bag against a component's declared props — unknown props + per-value domain
|
|
840
|
+
* checks. Generic: it resolves each prop's domain via {@link propValueDomain} and checks membership;
|
|
841
|
+
* no per-prop-type logic here. Returns the error strings (empty = valid). Lives in config so the
|
|
842
|
+
* `component/validate` op's truth is config-defined and the pipeline only renders it. */
|
|
843
|
+
function validateComponentProps(config, componentPath, props) {
|
|
844
|
+
const declared = resolveComponentProps(config, componentPath);
|
|
845
|
+
const errors = [];
|
|
846
|
+
for (const [name, value] of Object.entries(props)) {
|
|
847
|
+
const decl = declared[name];
|
|
848
|
+
if (!decl) {
|
|
849
|
+
errors.push(`unknown prop \`${name}\``);
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
const domain = propValueDomain(config, decl);
|
|
853
|
+
if (domain && !domain.accepts(String(value))) {
|
|
854
|
+
const shown = domain.leaves.slice(0, 8).map((leaf) => `'${leaf}'`).join(" | ");
|
|
855
|
+
const more = domain.leaves.length > 8 ? " | …" : "";
|
|
856
|
+
errors.push(`\`${name}\`: '${value}' not one of ${shown}${more}`);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
return errors;
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* The props one preview tile starts from: the component's `defaultProps` with its `previewProps`
|
|
863
|
+
* over the top.
|
|
864
|
+
*
|
|
865
|
+
* Preview wins, and that direction is the whole reason this exists once. `previewProps` is the state
|
|
866
|
+
* a person chose to show a component in — Button's `variant`, an Icon prop's example glyph — so a
|
|
867
|
+
* surface showing that tile has to agree with it, while `defaultProps` remains the baseline for every
|
|
868
|
+
* prop the tile doesn't pin. Get the order backwards and the panel reports a value the canvas isn't
|
|
869
|
+
* rendering, which is invisible until someone edits the wrong one.
|
|
870
|
+
*
|
|
871
|
+
* THE PRECEDENCE RULE, stated once: only a RENDER surface — a preview tile, the catalogue card, slot
|
|
872
|
+
* text seeding — reads this merge. An EDIT surface reads and writes the single field it owns
|
|
873
|
+
* (`defaultProps` OR `previewProps`, never the merge), and anything that SEEDS committed data — a
|
|
874
|
+
* prop binding, a forward's re-seed, a stored instance prop, a default-value form — reads raw
|
|
875
|
+
* `defaultProps`. A preview value that reaches any of those stops being staging and becomes a real
|
|
876
|
+
* default the moment it is saved, which is exactly the leak UDS-3514 closed. Before adding a caller,
|
|
877
|
+
* decide which of the three it is.
|
|
878
|
+
*
|
|
879
|
+
* Empty is the answer for a component that declares neither, and for a path that names no component
|
|
880
|
+
* at all — a caller asking about something absent gets "nothing pinned", not a crash.
|
|
881
|
+
*
|
|
882
|
+
* A fresh object per call, like every projection here. Callers that feed it to a React dep array
|
|
883
|
+
* memoize on the config and the path, which is what actually changes.
|
|
884
|
+
*
|
|
885
|
+
* Read through `Entity.body` rather than `Component.get`, which would be the typed way to ask: this
|
|
886
|
+
* module is imported by `entities/system/Component.ts` (for `resolveComponentProps` and
|
|
887
|
+
* `validateComponentProps`), so importing the class back would close a runtime cycle. `body` is the
|
|
888
|
+
* accessor that exists for a reader which can't reach its entity class, and it is not `toJSON` —
|
|
889
|
+
* serialization is for storage and the wire.
|
|
890
|
+
*/
|
|
891
|
+
function previewDefaults(config, path) {
|
|
892
|
+
const component = config.resolve("component", path);
|
|
893
|
+
if (!component) return {};
|
|
894
|
+
const { defaultProps, previewProps } = component.body;
|
|
895
|
+
return {
|
|
896
|
+
...defaultProps,
|
|
897
|
+
...previewProps
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* The preview grid a component implies.
|
|
902
|
+
*
|
|
903
|
+
* Nothing new is being said here: a style rule's `when` names a prop and a value, which is an axis
|
|
904
|
+
* and a point on it, and a rule's `_hover` block is a state worth a column. Authoring a matrix
|
|
905
|
+
* restated all of that beside the thing it was derived from, and the two could disagree — a variant
|
|
906
|
+
* added to `styles` and forgotten in the matrix just never appeared in the grid.
|
|
907
|
+
*
|
|
908
|
+
* A composite-typed prop is the same statement made relationally: each member of the group it
|
|
909
|
+
* references is a whole bag of style declarations, so sweeping the prop sweeps styles exactly the
|
|
910
|
+
* way a `when` does — the rules just never restate it. Left unchased, a component styled entirely
|
|
911
|
+
* through composites (a text primitive whose `variant` names a composite group) implies an empty
|
|
912
|
+
* grid, which reads as "no variants" rather than "fifty of them".
|
|
913
|
+
*
|
|
914
|
+
* So a component only authors what a preview can't know: the props one tile starts from.
|
|
915
|
+
*/
|
|
916
|
+
function previewMatrix(config, path) {
|
|
917
|
+
const component = config.resolve("component", path);
|
|
918
|
+
if (!component) return {
|
|
919
|
+
props: {},
|
|
920
|
+
modifiers: []
|
|
921
|
+
};
|
|
922
|
+
const { styles = {}, props: declared = {} } = component.toJSON();
|
|
923
|
+
const props = {};
|
|
924
|
+
const modifiers = /* @__PURE__ */ new Set();
|
|
925
|
+
for (const rule of Object.values(styles)) {
|
|
926
|
+
for (const [prop, stored] of Object.entries(rule.when ?? {})) {
|
|
927
|
+
const value = String(refLeaf(stored));
|
|
928
|
+
const values = props[prop] ?? [];
|
|
929
|
+
if (!values.includes(value)) values.push(value);
|
|
930
|
+
props[prop] = values;
|
|
931
|
+
}
|
|
932
|
+
for (const bag of Object.values(rule.layers ?? {})) for (const key of Object.keys(bag ?? {})) {
|
|
933
|
+
if (!key.startsWith("_")) continue;
|
|
934
|
+
if (modifierCategory(config, key.slice(1)) === "state") modifiers.add(key);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
for (const [name, decl] of Object.entries(declared)) {
|
|
938
|
+
if (decl?.type === "composite" && isRef(decl.value)) {
|
|
939
|
+
const values = declaredFirst([...memberLeaves(config, decl.value)], props[name] ?? []);
|
|
940
|
+
if (values.length > 0) props[name] = values;
|
|
941
|
+
}
|
|
942
|
+
if (decl?.type === "variant") {
|
|
943
|
+
const values = declaredFirst(isRef(decl.value) ? [] : listedIn(decl.value) ?? [], props[name] ?? []);
|
|
944
|
+
if (values.length > 0) props[name] = values;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
return {
|
|
948
|
+
props,
|
|
949
|
+
modifiers: [...modifiers]
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
/** The declared values in declaration order, then any rule-named value the declaration doesn't list. */
|
|
953
|
+
function declaredFirst(declared, ruleNamed) {
|
|
954
|
+
return [...declared, ...ruleNamed.filter((value) => !declared.includes(value))];
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* A prop's value as the component declares it, from the string a preview axis spells it as.
|
|
958
|
+
*
|
|
959
|
+
* `previewMatrix` names every axis value as a string (`'true'`, `'2'`), and a rule matches on the
|
|
960
|
+
* string form too, so a surface reading the grid never sees the declared type. Anything that writes
|
|
961
|
+
* a value back — a default chosen off a cell — restores it here, or a boolean prop's default is
|
|
962
|
+
* stored as the string `'true'` and never equals the `true` an author writes.
|
|
963
|
+
*/
|
|
964
|
+
function propValueFromAxis(config, path, prop, value) {
|
|
965
|
+
const declared = resolveComponentProps(config, path)[prop];
|
|
966
|
+
if (declared?.type === "boolean") return value === "true";
|
|
967
|
+
if (declared?.type === "number") {
|
|
968
|
+
const parsed = Number(value);
|
|
969
|
+
return Number.isFinite(parsed) ? parsed : value;
|
|
970
|
+
}
|
|
971
|
+
return value;
|
|
972
|
+
}
|
|
973
|
+
//#endregion
|
|
974
|
+
export { authoredBag, authoredValue, className, cssPropValue, cssVar, cssVarRef, declaredPropRouting, memberFromLeaf, memberLeaves, negatedCssValue, opacityPercentage, previewDefaults, previewMatrix, propOwner, propValueDomain, propValueFromAxis, renderStyleValue, resolveComponentProps, resolveFieldValue, resolveTokenValue, resolveTokenValueUnder, routedBag, sourceVarPrefix, styleAliasesOf, styleDeclarations, stylePropEntries, stylePropLeafForToken, stylePropNegates, stylePropTokenGroup, stylePropTokenPath, stylePropTokenValues, stylePropValueFromLeaf, stylePropValueLeaves, stylePropValues, stylePropertiesWriting, stylePropertyAccepts, stylePropertyFor, stylePropertyPathFor, validateComponentProps };
|