@theme-registry/refract 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/dist/_createTheme-255ff48e.esm.js +2 -0
  3. package/dist/_createTheme-255ff48e.esm.js.map +1 -0
  4. package/dist/_createTheme-41c01033.cjs.js +2 -0
  5. package/dist/_createTheme-41c01033.cjs.js.map +1 -0
  6. package/dist/_errors-be768b25.cjs.js +2 -0
  7. package/dist/_errors-be768b25.cjs.js.map +1 -0
  8. package/dist/_errors-d75a5c74.esm.js +2 -0
  9. package/dist/_errors-d75a5c74.esm.js.map +1 -0
  10. package/dist/adapter-kit.cjs.js +2 -0
  11. package/dist/adapter-kit.cjs.js.map +1 -0
  12. package/dist/adapter-kit.d.ts +88 -0
  13. package/dist/adapter-kit.esm.js +2 -0
  14. package/dist/adapter-kit.esm.js.map +1 -0
  15. package/dist/build/auditCommand.d.ts +19 -0
  16. package/dist/build/buildCommand.d.ts +23 -0
  17. package/dist/build/cli.d.ts +2 -0
  18. package/dist/build/config.d.ts +58 -0
  19. package/dist/build/diff.d.ts +56 -0
  20. package/dist/build/diffCommand.d.ts +29 -0
  21. package/dist/build/emit.d.ts +20 -0
  22. package/dist/build/emitTheme.d.ts +39 -0
  23. package/dist/build/guide.d.ts +34 -0
  24. package/dist/build/importCommand.d.ts +42 -0
  25. package/dist/build/index.d.ts +33 -0
  26. package/dist/build/init.d.ts +32 -0
  27. package/dist/build/paths.d.ts +42 -0
  28. package/dist/build/skillsCommand.d.ts +53 -0
  29. package/dist/build/tokensCommand.d.ts +17 -0
  30. package/dist/build/vendor.d.ts +39 -0
  31. package/dist/build.cjs.js +2 -0
  32. package/dist/build.cjs.js.map +1 -0
  33. package/dist/build.esm.js +2 -0
  34. package/dist/build.esm.js.map +1 -0
  35. package/dist/cli.js +8250 -0
  36. package/dist/cli.js.map +1 -0
  37. package/dist/color-math.cjs.js +2 -0
  38. package/dist/color-math.cjs.js.map +1 -0
  39. package/dist/color-math.esm.js +2 -0
  40. package/dist/color-math.esm.js.map +1 -0
  41. package/dist/core/ThemeAdapter.d.ts +234 -0
  42. package/dist/core/createTheme.d.ts +96 -0
  43. package/dist/core/cssProperties.d.ts +8 -0
  44. package/dist/core/defineAdapter.d.ts +25 -0
  45. package/dist/core/derive/crossProperty.d.ts +31 -0
  46. package/dist/core/derive/index.d.ts +11 -0
  47. package/dist/core/derive/resolveTokens.d.ts +27 -0
  48. package/dist/core/errors.d.ts +24 -0
  49. package/dist/core/index.d.ts +16 -0
  50. package/dist/core/media/containers.d.ts +20 -0
  51. package/dist/core/media/defaults.d.ts +3 -0
  52. package/dist/core/media/descriptors.d.ts +12 -0
  53. package/dist/core/media/index.d.ts +11 -0
  54. package/dist/core/media/queries.d.ts +20 -0
  55. package/dist/core/model/buildModel.d.ts +116 -0
  56. package/dist/core/model/index.d.ts +14 -0
  57. package/dist/core/model/interpreted.d.ts +37 -0
  58. package/dist/core/model/mergeRuleSet.d.ts +68 -0
  59. package/dist/core/model/model.d.ts +260 -0
  60. package/dist/core/noopAdapter.d.ts +26 -0
  61. package/dist/core/normalize/index.d.ts +15 -0
  62. package/dist/core/normalize/properties.d.ts +20 -0
  63. package/dist/core/normalize/recipeResolver.d.ts +19 -0
  64. package/dist/core/normalize/recipes.d.ts +38 -0
  65. package/dist/core/normalize/responsive.d.ts +40 -0
  66. package/dist/core/normalize/types.d.ts +261 -0
  67. package/dist/core/rawTheme.d.ts +53 -0
  68. package/dist/core/ref.d.ts +13 -0
  69. package/dist/core/subsystem.d.ts +115 -0
  70. package/dist/core/types.d.ts +31 -0
  71. package/dist/core/units.d.ts +93 -0
  72. package/dist/dtcg/export.d.ts +48 -0
  73. package/dist/dtcg/import.d.ts +43 -0
  74. package/dist/dtcg/index.d.ts +21 -0
  75. package/dist/dtcg/parse.d.ts +6 -0
  76. package/dist/dtcg/types.d.ts +69 -0
  77. package/dist/dtcg.cjs.js +2 -0
  78. package/dist/dtcg.cjs.js.map +1 -0
  79. package/dist/dtcg.esm.js +2 -0
  80. package/dist/dtcg.esm.js.map +1 -0
  81. package/dist/index.cjs.js +2 -0
  82. package/dist/index.cjs.js.map +1 -0
  83. package/dist/index.d.ts +27 -0
  84. package/dist/index.esm.js +2 -0
  85. package/dist/index.esm.js.map +1 -0
  86. package/dist/subsystems/animation/index.d.ts +13 -0
  87. package/dist/subsystems/animation/recipes.d.ts +20 -0
  88. package/dist/subsystems/animation/types.d.ts +52 -0
  89. package/dist/subsystems/borders/index.d.ts +10 -0
  90. package/dist/subsystems/borders/recipes.d.ts +29 -0
  91. package/dist/subsystems/borders/types.d.ts +50 -0
  92. package/dist/subsystems/colors/audit.d.ts +67 -0
  93. package/dist/subsystems/colors/colorInput.d.ts +17 -0
  94. package/dist/subsystems/colors/index.d.ts +12 -0
  95. package/dist/subsystems/colors/keywords.d.ts +9 -0
  96. package/dist/subsystems/colors/normalize.d.ts +26 -0
  97. package/dist/subsystems/colors/recipes.d.ts +21 -0
  98. package/dist/subsystems/colors/types.d.ts +98 -0
  99. package/dist/subsystems/colors/utils.d.ts +104 -0
  100. package/dist/subsystems/components/index.d.ts +13 -0
  101. package/dist/subsystems/components/recipes.d.ts +22 -0
  102. package/dist/subsystems/components/types.d.ts +46 -0
  103. package/dist/subsystems/effects/index.d.ts +9 -0
  104. package/dist/subsystems/effects/recipes.d.ts +24 -0
  105. package/dist/subsystems/effects/types.d.ts +63 -0
  106. package/dist/subsystems/effects/utils.d.ts +57 -0
  107. package/dist/subsystems/globals/index.d.ts +21 -0
  108. package/dist/subsystems/globals/presets.d.ts +33 -0
  109. package/dist/subsystems/globals/types.d.ts +48 -0
  110. package/dist/subsystems/index.d.ts +21 -0
  111. package/dist/subsystems/layout/index.d.ts +14 -0
  112. package/dist/subsystems/layout/normalize.d.ts +35 -0
  113. package/dist/subsystems/layout/recipes.d.ts +16 -0
  114. package/dist/subsystems/layout/structural.d.ts +37 -0
  115. package/dist/subsystems/layout/types.d.ts +154 -0
  116. package/dist/subsystems/typography/index.d.ts +9 -0
  117. package/dist/subsystems/typography/normalize.d.ts +17 -0
  118. package/dist/subsystems/typography/recipes.d.ts +21 -0
  119. package/dist/subsystems/typography/types.d.ts +49 -0
  120. package/package.json +83 -0
  121. package/skills/adapter-scaffold/SKILL.md +85 -0
  122. package/skills/adapter-usage/SKILL.md +75 -0
  123. package/skills/build-config/SKILL.md +77 -0
  124. package/skills/colors/SKILL.md +134 -0
  125. package/skills/consuming-the-output/SKILL.md +62 -0
  126. package/skills/dtcg-import/SKILL.md +76 -0
  127. package/skills/layout/SKILL.md +65 -0
  128. package/skills/overrides-and-child-themes/SKILL.md +70 -0
  129. package/skills/recipes-and-composition/SKILL.md +110 -0
  130. package/skills/theme-authoring/SKILL.md +107 -0
  131. package/skills/theme-foundations/SKILL.md +127 -0
  132. package/skills/troubleshooting/SKILL.md +97 -0
  133. package/skills/typography/SKILL.md +66 -0
  134. package/skills/visual-effects/SKILL.md +96 -0
  135. package/src/subsystems/colors/utils.ts +299 -0
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Recipe-group normalization.
3
+ *
4
+ * A recipe authors its base declarations inline, plus two optional condition axes — a
5
+ * grouped `states` map and a `responsive[]` list. Normalization splits both off the base
6
+ * and flattens them into one unified `overrides` list, each entry carrying an optional
7
+ * `state` and/or `breakpoint` (both present = the cross-product). References
8
+ * (`breakpoint` / `state` / `variant` / `target`) are validated against the allowed sets.
9
+ *
10
+ * §7A — before any of that, a pre-pass expands an optional `variants` modifier map on any
11
+ * recipe into flat sibling recipes (`<recipe>-<variant>`), so the rest of this module — the
12
+ * resolver, `interpretRecipe`, reference extraction, the Model, and every adapter — sees the
13
+ * same `RecipeGroupDefinition` authored today. Gated on the `variants` key: a group with no
14
+ * `variants` is returned by reference, byte-identical.
15
+ */
16
+ import type { RecipeGroupDefinition, NormalizedRecipeGroup } from "./types";
17
+ /** Allowed-reference sets threaded into recipe normalization for validation. */
18
+ export type RecipeNormalizationOptions<TBreakpoint extends string> = {
19
+ propertyPath?: string;
20
+ allowedBreakpoints?: ReadonlyArray<TBreakpoint> | ReadonlySet<TBreakpoint>;
21
+ /** The adapter's known-state set; `state` values are validated against it (throw on unknown). */
22
+ allowedStates?: ReadonlyArray<string> | ReadonlySet<string>;
23
+ /** Container queries (§10.5): container name → its allowed size-names, validated on container overrides. */
24
+ allowedContainers?: ReadonlyMap<string, ReadonlySet<string>>;
25
+ };
26
+ /**
27
+ * Normalize a whole recipe group (variant name → variant definition).
28
+ *
29
+ * Sibling variant names become the group's `allowedVariants` / `allowedTargets` set,
30
+ * so a variant's responsive `variant` / `target` refs are validated against its peers.
31
+ *
32
+ * @param group The authored group — `{ primary: {…}, danger: {…} }`.
33
+ * @param options `allowedBreakpoints` / `allowedStates` + `propertyPath` for error labels.
34
+ * @returns Each variant normalized to `{ base, responsive }` (states + responsive flattened).
35
+ * @throws If any entry references an unknown breakpoint / state / variant / target,
36
+ * or a responsive entry is missing its breakpoint.
37
+ */
38
+ export declare const normalizeRecipeGroup: <TProps extends Record<string, unknown>, TBreakpoint extends string>(group: RecipeGroupDefinition<TProps, TBreakpoint>, options?: RecipeNormalizationOptions<TBreakpoint>) => NormalizedRecipeGroup<TProps, TBreakpoint>;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Responsive-override normalization + reference validation.
3
+ *
4
+ * Normalizes a property's `responsive[]` entries (defaulting each `query`) and
5
+ * validates their `breakpoint` / `variant` / `target` references against the
6
+ * allowed sets threaded in via the normalization context.
7
+ */
8
+ import type { NormalizedPropertyValue, NormalizedResponsiveOverride, PropertyNormalizationOptions, ResponsiveOverride, ResponsiveQuery } from "./types";
9
+ /** The `query` a responsive entry gets when none is authored — an exact-width media match. */
10
+ export declare const DEFAULT_RESPONSIVE_QUERY: ResponsiveQuery;
11
+ type AllowedValues<T> = ReadonlyArray<T> | ReadonlySet<T>;
12
+ type ResponsiveNormalizationContext<TBreakpoint extends string> = Pick<PropertyNormalizationOptions<unknown, TBreakpoint>, "propertyPath" | "allowedBreakpoints"> & {
13
+ allowedTargets?: AllowedValues<string>;
14
+ allowedVariants?: AllowedValues<string>;
15
+ };
16
+ /**
17
+ * Normalize a property's `responsive[]` list: default each entry's `query` and
18
+ * validate its references against the context's allowed sets.
19
+ *
20
+ * @param overrides The authored responsive entries (or `undefined` / empty → `[]`).
21
+ * @param context Optional allowed sets — `allowedBreakpoints` / `allowedVariants`
22
+ * / `allowedTargets` — plus `propertyPath` for error labels.
23
+ * @throws If an entry is missing a `breakpoint`, or references an unknown
24
+ * breakpoint / variant / target.
25
+ */
26
+ export declare function normalizeResponsiveOverrides<TValue, TExtra extends Record<string, unknown> = Record<string, never>, TBreakpoint extends string = string>(overrides: ResponsiveOverride<TValue, TExtra, TBreakpoint>[] | undefined, context?: ResponsiveNormalizationContext<TBreakpoint>): NormalizedResponsiveOverride<TValue, TExtra, TBreakpoint>[];
27
+ /**
28
+ * Second-pass validation: once a property is fully normalized, confirm every
29
+ * responsive `variant` / `target` names a variant that actually exists on it.
30
+ *
31
+ * Complements {@link normalizeResponsiveOverrides}, which can't see the property's
32
+ * own variant set during the first pass (variants are normalized in parallel).
33
+ * A no-op when the property declares no variants.
34
+ *
35
+ * @throws If a responsive entry references a variant/target with no matching definition.
36
+ */
37
+ export declare function validateNormalizedResponsiveRefs<TValue, TExtra extends Record<string, unknown> = Record<string, never>, TBreakpoint extends string = string>(normalized: NormalizedPropertyValue<TValue, TExtra, TBreakpoint>, options?: {
38
+ propertyPath?: string;
39
+ }): void;
40
+ export {};
@@ -0,0 +1,261 @@
1
+ /**
2
+ * The normalize-layer vocabulary — the authored-input and normalized-output
3
+ * shapes the normalizer speaks. Distinct from the Model types (`../model`): these
4
+ * describe raw theme config and its normalized form; the Model is the downstream
5
+ * format-neutral representation built from them (Step 0c).
6
+ *
7
+ * Ported verbatim from the proven `core/common/types.ts` (self-contained, no imports).
8
+ */
9
+ export type PrimitiveValue = string | number;
10
+ export type Breakpoints<TKey extends string = string> = Record<TKey, number>;
11
+ export type NormalizedBreakpoints<TKey extends string = string> = Record<TKey, {
12
+ base: number;
13
+ }>;
14
+ /** `container-type` on a named query container. `inline-size` (default) contains the inline axis only. */
15
+ export type ContainerType = "inline-size" | "size";
16
+ /**
17
+ * One named container (§10.5) — its `container-type` + its threshold scale (`sizeName → px`). The
18
+ * config key is the CSS `container-name`. Parallel to a breakpoint scale, but a *component-slot*
19
+ * scale, not a device scale. A recipe override references it as `{ container: <name>, size: <sizeName> }`.
20
+ */
21
+ export type ContainerDefinition = {
22
+ type?: ContainerType;
23
+ sizes: Record<string, number>;
24
+ };
25
+ /** The authored `rawTheme.containers` map — `containerName → {@link ContainerDefinition}`. */
26
+ export type Containers = Record<string, ContainerDefinition>;
27
+ export type ResponsiveQuery = "min" | "max" | "exact";
28
+ export type ResponsiveOrientation = "landscape" | "portrait";
29
+ export type ResponsiveOverride<TValue, TExtra extends Record<string, unknown> = {}, TBreakpoint extends string = string> = {
30
+ breakpoint: TBreakpoint;
31
+ query?: ResponsiveQuery;
32
+ /** dec.5 — READ source: at this breakpoint the destination var swaps to this variant's var
33
+ * (`var(--<prop>-<ref>)`). Replaces the old swap-only `variant`. Composes with `target` (write). */
34
+ ref?: string;
35
+ /** dec.9 — appearance-mode CONDITION: the override applies only in this mode (mirror of a recipe
36
+ * responsive's `state`). A property-side axis; validated against the `modes` registry (dec.1). */
37
+ mode?: string;
38
+ target?: string;
39
+ orientation?: ResponsiveOrientation;
40
+ } & Partial<{
41
+ base: TValue;
42
+ } & TExtra>;
43
+ export type NormalizedResponsiveOverride<TValue, TExtra extends Record<string, unknown> = {}, TBreakpoint extends string = string> = Omit<ResponsiveOverride<TValue, TExtra, TBreakpoint>, "query"> & {
44
+ query: ResponsiveQuery;
45
+ };
46
+ export type VariantValue<TValue, TExtra extends Record<string, unknown> = {}, TBreakpoint extends string = string> = TValue | ({
47
+ base: TValue;
48
+ } & TExtra);
49
+ /** dec.2 — one modifier dial in a derivation chain: a single-key object `{ [fn]: args }`
50
+ * (`{ darken: 10 }`, `{ alpha: 40 }`, `{ adjust: { l, c, h } }`). */
51
+ export type DerivationModifier = Record<string, unknown>;
52
+ /**
53
+ * A derivation (§10.3, dec.2) — read `ref` (defaults to the property's OWN base), then fold the
54
+ * ordered `modifiers` chain of `{ [fn]: args }` dials over it: `value = modifiers.reduce((v, {fn,arg})
55
+ * => fn(v, arg), resolve(ref))`. The owning subsystem hook (colors) bakes it (mirror of tonal steps).
56
+ * Replaces the old single `{ fn, arg }` shorthand.
57
+ */
58
+ export type ModeDerivation = {
59
+ ref?: string;
60
+ modifiers: DerivationModifier[];
61
+ };
62
+ /**
63
+ * One authored appearance-mode override (§10.3) — the value payload of a {@link ModeOverride}. A
64
+ * `{ ref?, modifiers }` derivation derives the base; a `{ base?, …extra }` object overrides the base
65
+ * (literal) and/or sibling extras (literal only in v1). Parallel to {@link VariantValue}. (The bare
66
+ * `TValue` shorthand is gone — modes are an array of `{ mode, … }` entries, so the value is spelled
67
+ * with an explicit `base:`.)
68
+ */
69
+ export type ModeValue<TValue, TExtra extends Record<string, unknown> = {}> = (ModeDerivation & Partial<TExtra>) | ({
70
+ base?: TValue;
71
+ } & Partial<TExtra>);
72
+ /**
73
+ * One entry in a property's `modes` **array** — its `mode` name (the WHEN, validated against the
74
+ * `modes` registry, dec.1), an optional `target` (the WHERE — scope the override into a variant's
75
+ * var), plus the {@link ModeValue} payload (the WHAT). Mirrors a {@link ResponsiveOverride} entry;
76
+ * shares the WHEN/WHERE/WHAT spine with recipe states.
77
+ */
78
+ export type ModeOverride<TValue, TExtra extends Record<string, unknown> = {}> = {
79
+ mode: string;
80
+ target?: string;
81
+ } & ModeValue<TValue, TExtra>;
82
+ export type ExtendedProperty<TValue, TExtra extends Record<string, unknown> = {}, TBreakpoint extends string = string> = {
83
+ base: TValue;
84
+ responsive?: ResponsiveOverride<TValue, TExtra, TBreakpoint>[];
85
+ variants?: Record<string, VariantValue<TValue, TExtra, TBreakpoint>>;
86
+ modes?: ModeOverride<TValue, TExtra>[];
87
+ } & TExtra;
88
+ /**
89
+ * §W6b — an **external-token** property: a passthrough to a CSS variable a *parent* theme owns.
90
+ * `external` is a refract token path (`"colors.brand"` → `var(--<extends.prefix>-colors-brand)`) or a
91
+ * literal CSS variable (`"--mat-sys-bg"`, leading `--`). The rest of the theme references it like any
92
+ * token, but it is never defined locally and is not tonally derivable.
93
+ */
94
+ export type ExternalProperty = {
95
+ external: string;
96
+ };
97
+ export type PropertyValue<TValue, TExtra extends Record<string, unknown> = {}, TBreakpoint extends string = string> = TValue | ExtendedProperty<TValue, TExtra, TBreakpoint> | ExternalProperty;
98
+ export type NormalizedVariantValue<TValue, TExtra extends Record<string, unknown> = {}, TBreakpoint extends string = string> = {
99
+ /**
100
+ * The variant's primary value. Normally present (a literal, or the cached resolution of a
101
+ * `derive`). **Absent** only for a dec.4 **cross-property** derivation, whose source lives in
102
+ * another property and so cannot be baked during this property's single normalize pass — the
103
+ * post-build `bakeCrossPropertyDerivations` pass fills it (via `derive.ref` + `derive.modifiers`).
104
+ */
105
+ base?: TValue;
106
+ /**
107
+ * Synthesized variants (e.g. colors' `light`/`dark` steps) carry a fully-qualified derivation
108
+ * so the Model can store them as a derived `Ref` (`{ ref, fn, arg }`) instead of an opaque baked
109
+ * value — enabling free override propagation. Set by a subsystem's `normalizeProperty`; the
110
+ * `base` value is retained (it's the cached resolution the CSS lowering still reads) — except for
111
+ * a cross-property `derive`, where `base` is filled post-build (see above).
112
+ */
113
+ derive?: {
114
+ ref: string;
115
+ fn?: string;
116
+ arg?: unknown;
117
+ modifiers?: Array<{
118
+ fn: string;
119
+ arg?: unknown;
120
+ }>;
121
+ };
122
+ } & TExtra;
123
+ /**
124
+ * One appearance mode after normalization (§10.3). `base` is the baked literal (core fills it
125
+ * for a literal mode; the colors hook fills it for a derived one) — absent when the mode overrides
126
+ * only extras. `derive` carries a base derivation the colors hook resolves/bakes (`ref` filled by
127
+ * the subsystem that owns the fn). Extra fields are literal siblings (e.g. colors' `text`).
128
+ */
129
+ export type NormalizedModeValue<TValue, TExtra extends Record<string, unknown> = {}> = {
130
+ base?: TValue;
131
+ derive?: {
132
+ ref?: string;
133
+ fn?: string;
134
+ arg?: unknown;
135
+ modifiers?: Array<{
136
+ fn: string;
137
+ arg?: unknown;
138
+ }>;
139
+ };
140
+ } & Partial<TExtra>;
141
+ /** One entry in the normalized `modes` **array** — the mode name + optional target + the normalized
142
+ * value payload. Mirrors {@link NormalizedResponsiveOverride}. */
143
+ export type NormalizedModeOverride<TValue, TExtra extends Record<string, unknown> = {}> = {
144
+ mode: string;
145
+ target?: string;
146
+ } & NormalizedModeValue<TValue, TExtra>;
147
+ export type NormalizedPropertyValue<TValue, TExtra extends Record<string, unknown> = {}, TBreakpoint extends string = string> = {
148
+ base: TValue;
149
+ responsive: NormalizedResponsiveOverride<TValue, TExtra, TBreakpoint>[];
150
+ variants?: Record<string, NormalizedVariantValue<TValue, TExtra, TBreakpoint>>;
151
+ modes?: NormalizedModeOverride<TValue, TExtra>[];
152
+ /** §W6b — set (to the literal parent var name) when this property is an external-token passthrough. */
153
+ external?: string;
154
+ } & TExtra;
155
+ export type PropertyNormalizationOptions<TValue, TBreakpoint extends string = string> = {
156
+ propertyPath?: string;
157
+ coerceValue?: (value: TValue) => TValue;
158
+ fallbackBase?: TValue;
159
+ allowedBreakpoints?: ReadonlyArray<TBreakpoint> | ReadonlySet<TBreakpoint>;
160
+ /**
161
+ * §15 object-leaf subsystems (effects shadow / transitions): the set of value-field names that,
162
+ * when present at a property/variant top level WITHOUT an explicit `base` key, are assembled into
163
+ * the base value object. Keys not in this set (and not the structural `variants`/`responsive`/
164
+ * `modes`) are treated as `TExtra` siblings. An explicit `base` key is the escape hatch — it is
165
+ * the base of any shape (object/array/string), bypassing assembly. Absent for scalar subsystems.
166
+ */
167
+ leafFields?: readonly string[];
168
+ };
169
+ export type RecipeResponsiveOverride<TProps extends Record<string, unknown>, TBreakpoint extends string = string> = {
170
+ /** Present for breakpoint-conditioned overrides; absent for pure-state / container overrides. */
171
+ breakpoint?: TBreakpoint;
172
+ query?: ResponsiveQuery;
173
+ /** Recipe-only condition axis; validated against the adapter's known-state set in normalize. */
174
+ state?: string;
175
+ variant?: string;
176
+ target?: string;
177
+ orientation?: ResponsiveOrientation;
178
+ /** Container-query axis (§10.5): the named container this override responds to (replaces `breakpoint`). */
179
+ container?: string;
180
+ /** The size-name in that container's threshold scale (used with `container`, like `breakpoint`). */
181
+ size?: string;
182
+ } & Partial<TProps>;
183
+ /**
184
+ * One entry in a recipe / globals-element `states` **array** — its `state` name (WHEN, validated
185
+ * against the adapter's known-state set), an optional `target` (WHERE — scope the override onto the
186
+ * `<item>-<target>` sibling selector, dec.8), plus the flat recipe-prop deltas (WHAT). Mirrors a
187
+ * {@link RecipeResponsiveOverride} entry with no breakpoint; shares the WHEN/WHERE/WHAT spine with a
188
+ * property {@link ModeOverride}. A list (not a `state → delta` map) so two same-state entries can
189
+ * target different siblings.
190
+ */
191
+ export type RecipeStateOverride<TProps extends Record<string, unknown>> = {
192
+ state: string;
193
+ target?: string;
194
+ } & Partial<TProps>;
195
+ /**
196
+ * The authored `states` value — the canonical **array** of {@link RecipeStateOverride}s (dec.8), or a
197
+ * legacy `{ state: {…delta} }` **map** (no `target`, one entry per state) still accepted for authoring
198
+ * convenience. Both normalize to the same flattened override list; docs teach the array form.
199
+ */
200
+ export type RecipeStates<TProps extends Record<string, unknown>> = RecipeStateOverride<TProps>[] | Record<string, Partial<TProps>>;
201
+ /**
202
+ * Strip a string/number index signature from `T`, keeping only its explicitly
203
+ * declared keys. Needed so the reserved `responsive` / `states` keys below aren't
204
+ * clobbered by a recipe-prop index signature like `[property: string]: string`
205
+ * (which would otherwise force them to be `undefined` and reject authoring).
206
+ */
207
+ type RemoveIndexSignature<T> = {
208
+ [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K];
209
+ };
210
+ /**
211
+ * dec.7 — one modifier delta inside a **recipe's** `variants` map (§7A). Layered onto the recipe's
212
+ * own props by `mergeRecipe` to desugar into a flat sibling `<recipe>-<variant>` recipe. **FLAT**: a
213
+ * prop delta (delta wins) or `null` to drop an inherited ref. A variant's conditional behaviour lives
214
+ * in the ITEM's `states`/`responsive` — NOT nested here (no nested `states`/`responsive`/`variants`).
215
+ * (A property variant is a token value → flat; a recipe variant is a sibling recipe → still authored
216
+ * flat here, its states come from the item. Globals is the exception, see {@link GlobalsElementVariantModifier}.)
217
+ */
218
+ export type RecipeVariantModifier<TProps extends Record<string, unknown>, TBreakpoint extends string = string> = Partial<TProps> & {
219
+ /** A prop delta (delta wins) or `null` to drop an inherited ref. */
220
+ [property: string]: unknown;
221
+ };
222
+ /**
223
+ * §9 — one modifier delta inside a **globals element's** `variants` map. Unlike a recipe variant
224
+ * (dec.7, flat), a globals element variant KEEPS the conditional axes (`states`/`responsive`) — each
225
+ * desugars into a higher-specificity element variant (`a.subtle`) that can carry its own hover, etc.
226
+ * Globals is the non-generic exception to the flat recipe-variant rule.
227
+ */
228
+ export type GlobalsElementVariantModifier<TProps extends Record<string, unknown>, TBreakpoint extends string = string> = {
229
+ responsive?: RecipeResponsiveOverride<TProps, TBreakpoint>[];
230
+ states?: RecipeStates<TProps>;
231
+ /** A prop delta (delta wins) or `null` to drop an inherited ref. */
232
+ [property: string]: unknown;
233
+ };
234
+ export type RecipeVariantDefinition<TProps extends Record<string, unknown>, TBreakpoint extends string = string,
235
+ /** dec.7 — the variant-modifier shape. Recipes default to the FLAT modifier; globals pass the rich one. */
236
+ TVariantMod = RecipeVariantModifier<TProps, TBreakpoint>> = RemoveIndexSignature<TProps> & {
237
+ responsive?: RecipeResponsiveOverride<TProps, TBreakpoint>[];
238
+ /** Grouped state condition map — `{ hover: {…decls}, disabled: {…decls} }`; flattened into overrides. */
239
+ states?: RecipeStates<TProps>;
240
+ /**
241
+ * §7A — modifier deltas layered on this recipe/element. A pre-pass in `normalizeRecipeGroup` expands
242
+ * each into a flat sibling named `<recipe>-<variant>` (via `mergeRecipe`), then the ordinary
243
+ * normalization runs unchanged. Reserved, like `states` / `responsive`.
244
+ */
245
+ variants?: Record<string, TVariantMod>;
246
+ /** Additional style declarations beyond the named recipe props. */
247
+ [property: string]: unknown;
248
+ };
249
+ export type RecipeGroupDefinition<TProps extends Record<string, unknown>, TBreakpoint extends string = string> = Record<string, RecipeVariantDefinition<TProps, TBreakpoint>>;
250
+ /**
251
+ * The authored `recipes:` block — a map of group name → {@link RecipeGroupDefinition}
252
+ * (`{ solid: { primary: {…}, danger: {…} }, outline: {…} }`). The per-subsystem
253
+ * authoring-input types (`ColorsRaw`/… ) key their `recipes?` on this.
254
+ */
255
+ export type RecipeBlock<TProps extends Record<string, unknown>, TBreakpoint extends string = string> = Record<string, RecipeGroupDefinition<TProps, TBreakpoint>>;
256
+ export type NormalizedRecipeVariant<TProps extends Record<string, unknown>, TBreakpoint extends string = string> = {
257
+ base: TProps;
258
+ responsive: RecipeResponsiveOverride<TProps, TBreakpoint>[];
259
+ };
260
+ export type NormalizedRecipeGroup<TProps extends Record<string, unknown>, TBreakpoint extends string = string> = Record<string, NormalizedRecipeVariant<TProps, TBreakpoint>>;
261
+ export {};
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The `RawTheme` authoring type (§8a) — the top-level shape a user's raw theme is authored in.
3
+ *
4
+ * The raw theme is the file users *live in* (edited/extended constantly), so it's richly typed for
5
+ * autocomplete + typo detection. It composes the per-subsystem authoring-input types (each defined
6
+ * beside its subsystem and built from the `core/normalize` vocabulary) under their nested keys.
7
+ *
8
+ * **Strict-closed** (no top-level index signature): an unknown/misspelled top-level key is a type
9
+ * error on a literal — that's the whole point. The `createTheme` spine still ignores unknown
10
+ * top-level keys at runtime, and a power user who genuinely needs to bypass can cast. Breakpoint
11
+ * names stay loose `string` in v1 (TS can't infer them from the sibling `breakpoints` key within one
12
+ * literal anyway) — the normalize vocabulary underneath is generic, so generic threading is a
13
+ * documented future enhancement.
14
+ *
15
+ * Authoring type ONLY — no runtime/output change. `createTheme` bridges to its loose spine with a
16
+ * single boundary cast; the subsystem hooks still consume each `rawTheme[key]` slice as
17
+ * `Record<string, unknown>`.
18
+ */
19
+ import type { Breakpoints, Containers } from "./normalize";
20
+ import type { ColorsRaw, TypographyRaw, EffectsRaw, BordersRaw, AnimationRaw, LayoutRaw, ComponentsRaw, GlobalsRaw } from "../subsystems";
21
+ export interface RawTheme {
22
+ /**
23
+ * §W6b — borrow tokens from a *parent* theme you don't own. `prefix` is that theme's CSS-variable
24
+ * prefix, so a path-form external (`{ external: "colors.brand" }`) lowers to `var(--<prefix>-colors-brand)`.
25
+ * A literal-form external (`{ external: "--mat-sys-bg" }`) needs no prefix. Defaults to `"dt"`.
26
+ */
27
+ extends?: {
28
+ prefix?: string;
29
+ };
30
+ /** Breakpoint name → min-width (px). Omit for the default breakpoints. */
31
+ breakpoints?: Breakpoints;
32
+ /** Named query containers (§10.5) — `name → { type?, sizes }`; referenced by recipe container overrides. */
33
+ containers?: Containers;
34
+ /**
35
+ * Appearance-mode registry — the declared set of mode names. Every property-level `modes` key is
36
+ * validated against this (typo-detection, like breakpoints/containers). Omit for the default
37
+ * `["dark", "light"]`. `dark`/`light` auto-bind to `@media (prefers-color-scheme)`; any other name
38
+ * is a `[data-theme="…"]` manual toggle only.
39
+ */
40
+ modes?: string[];
41
+ colors?: ColorsRaw;
42
+ typography?: TypographyRaw;
43
+ effects?: EffectsRaw;
44
+ /** Stroke geometry (width/style/offset/radius) + border/outline recipes (§14). Color = value-level `colors.*` ref. */
45
+ borders?: BordersRaw;
46
+ /** Motion tokens (duration/easing/delay) + keyframes + animation-shorthand recipes (§10.2). */
47
+ animation?: AnimationRaw;
48
+ layout?: LayoutRaw;
49
+ components?: ComponentsRaw;
50
+ /** Bare-element base layer (§9) — a normalization `preset` + themed `elements` (declarations +
51
+ * states / responsive / variants) bound to raw selectors. */
52
+ globals?: GlobalsRaw;
53
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `ref(path)` — author a token **reference** inside a `css` delta or a globals element rule.
3
+ *
4
+ * Inside a `css` block (and globals element declarations) a bare string is a **literal** CSS value —
5
+ * `display: "flex"` emits `flex`. To emit a token reference instead, wrap the token path in `ref()`:
6
+ * `color: ref("colors.brand.text")` → `var(--…)`. The JSON-safe equivalent is the plain object form
7
+ * `{ ref: "colors.brand.text" }`; both produce the same Model reference, so a `theme.raw.json` needs no
8
+ * function. (Top-level recipe composition fields — `colors: "solid.brand"` — are unaffected: a bare
9
+ * string there is always a reference.)
10
+ */
11
+ export declare const ref: (path: string) => {
12
+ ref: string;
13
+ };
@@ -0,0 +1,115 @@
1
+ /**
2
+ * The minimal subsystem descriptor (Step 0d).
3
+ *
4
+ * `createTheme`'s spine loops a `SUBSYSTEMS` list of these — never subsystem-specific
5
+ * control flow. Growing the pipeline = pushing a `Subsystem` (or filling a hook on the
6
+ * type), so the spine's shape is fixed while its coverage grows step by step.
7
+ *
8
+ * Day one carries a single hook — `normalizeProperties` — enough for the colors
9
+ * property slice. Recipe / slice hooks land alongside their steps (recipes: step 1+).
10
+ */
11
+ import type { NormalizedPropertyValue, NormalizedRecipeVariant } from "./normalize";
12
+ import type { DerivationRegistry } from "./derive";
13
+ import type { InterpretedRecipeVariant, Keyframe, PropertyModel, RuleSetGroup } from "./model";
14
+ import type { MediaConfig, MediaDescriptor } from "./media";
15
+ /** A subsystem's normalized property collection (`name → normalized property`). */
16
+ export type NormalizedProperties = Record<string, NormalizedPropertyValue<unknown, Record<string, unknown>>>;
17
+ /** Context threaded into a subsystem's normalize hooks by the spine. */
18
+ export interface SubsystemNormalizeContext {
19
+ /** The theme's breakpoint keys — responsive references are validated against these. */
20
+ readonly allowedBreakpoints: string[];
21
+ }
22
+ /** Context threaded into a subsystem's `buildStructural` hook by the spine. */
23
+ export interface StructuralContext {
24
+ readonly breakpoints: Record<string, number>;
25
+ readonly media: MediaDescriptor<string>;
26
+ /** Media unit config (§10.5) — so breakpoint-DERIVED lengths (fixed-container widths) can format in
27
+ * the same px/em/rem unit the `@media` thresholds use. Absent → px. */
28
+ readonly mediaConfig?: MediaConfig;
29
+ /**
30
+ * The adapter's known-state set (§9) — a structural subsystem whose rule-sets carry recipe-style
31
+ * condition axes (globals' themed element `states`) validates its `state:` refs against this, the
32
+ * same set the generic recipe path uses. Absent ⇒ no state validation (generator subsystems that
33
+ * emit no conditions, e.g. layout).
34
+ */
35
+ readonly allowedStates?: ReadonlyArray<string>;
36
+ /** Container queries (§10.5) — `name → allowed size-names`, validated on a structural rule-set's
37
+ * container overrides (globals element `responsive` container entries). */
38
+ readonly allowedContainers?: ReadonlyMap<string, ReadonlySet<string>>;
39
+ }
40
+ /**
41
+ * A subsystem's structural (generator-driven) Model contribution — rule-set groups that aren't
42
+ * authored recipes (layout's columns/grids/stacks/container), plus already-built config
43
+ * `PropertyModel`s to merge into the subsystem's tokens (Model `extraProperties`). Declarations
44
+ * carry token-path refs, never CSS — the CSS adapter lowers them.
45
+ */
46
+ export interface StructuralOutput {
47
+ readonly ruleSetGroups: Record<string, RuleSetGroup>;
48
+ readonly configProperties: Record<string, PropertyModel>;
49
+ /**
50
+ * Named keyframe definitions (§10.2) — the animation subsystem's contribution. Neither a
51
+ * rule-set nor a config token, so it rides its own slot; threaded into
52
+ * {@link SubsystemModelInput.keyframes} and attached to {@link SubsystemModel.keyframes}. Absent
53
+ * ⇒ no keyframes (the common case — layout/reset return none).
54
+ */
55
+ readonly keyframes?: Record<string, Keyframe>;
56
+ }
57
+ /**
58
+ * Context threaded into a subsystem's `interpretRecipe` hook by the generic recipe path.
59
+ *
60
+ * The subsystem resolves each recipe declaration against its **own** normalized
61
+ * `properties` (reference-syntax knowledge is subsystem-specific), emitting token-path
62
+ * refs. `resolveRecipeVariant` pulls a sibling's already-interpreted form (cycle-safe).
63
+ */
64
+ export interface RecipeInterpretContext {
65
+ readonly breakpoints: Record<string, number>;
66
+ readonly media: MediaDescriptor<string>;
67
+ /** The subsystem's own normalized property collection — the reference resolution source. */
68
+ readonly properties: NormalizedProperties;
69
+ /** Resolve a sibling variant in the same group (for responsive `variant:` swaps). */
70
+ readonly resolveRecipeVariant: (variantName: string) => InterpretedRecipeVariant<string>;
71
+ /** `"<subsystem>.recipes.<group>"` — used for error labels. */
72
+ readonly groupPath: string;
73
+ }
74
+ /** A subsystem plugged into the `createTheme` spine. */
75
+ export interface Subsystem {
76
+ /** The `rawTheme[key]` slice this subsystem owns (also its Model namespace). */
77
+ readonly key: string;
78
+ /**
79
+ * Normalize the subsystem's raw property slice into the canonical shape the Model
80
+ * builder consumes. Reserved sub-keys (e.g. `recipes`) are the subsystem's to skip.
81
+ */
82
+ normalizeProperties(rawSlice: Record<string, unknown> | undefined, ctx: SubsystemNormalizeContext): NormalizedProperties;
83
+ /**
84
+ * Interpret one normalized recipe variant into declaration {@link InterpretedRecipeVariant}
85
+ * refs. Required for a subsystem with recipes; the generic recipe path (in `createTheme`)
86
+ * drives it through the shared normalize + cycle-safe resolver, then builds the Model
87
+ * rule-sets from the interpreted output. Absent ⇒ the subsystem contributes no rule-sets.
88
+ */
89
+ interpretRecipe?(variantName: string, variant: NormalizedRecipeVariant<Record<string, unknown>, string>, ctx: RecipeInterpretContext): InterpretedRecipeVariant<string>;
90
+ /**
91
+ * Extract cross-subsystem recipe references (`"colors:solid.primary"`, …) from a normalized
92
+ * variant's base — the composition (components) subsystem's hook. When present, the generic recipe
93
+ * path builds each rule-set via `buildComponentRuleSetFromInterpreted` (keeping the references as
94
+ * Model pointers, so the referenced recipe's own states ride along on its shared class) instead of
95
+ * the plain `buildRuleSetFromInterpreted`. Absent ⇒ a subsystem contributes no references (the
96
+ * common case). Keeps the spine generic: the recipe path is reference-aware without a
97
+ * components-specific branch.
98
+ */
99
+ extractReferences?(normalizedVariantBase: Record<string, unknown>): string[];
100
+ /**
101
+ * Emit generator-driven rule-sets + config tokens (layout's columns/grids/stacks/container).
102
+ * The spine calls this once per subsystem that provides it, merging the structural rule-set
103
+ * groups **ahead of** the recipe groups and passing the config tokens as `extraProperties`.
104
+ * Absent ⇒ the subsystem has no structural output. Kept generic: no subsystem-specific
105
+ * control flow in the spine — pushing a subsystem with this hook is enough.
106
+ */
107
+ buildStructural?(rawSlice: Record<string, unknown>, ctx: StructuralContext): StructuralOutput;
108
+ /**
109
+ * The subsystem's value-derivation fns (`fn name -> DerivationFn`), keeping the spine
110
+ * generic: the spine collects `derivations` across `SUBSYSTEMS` into one registry rather
111
+ * than importing any subsystem's fns. Colors contributes `{ lighten, darken }`; a subsystem
112
+ * with no synthesized steps omits it. Duplicate fn names across subsystems throw.
113
+ */
114
+ readonly derivations?: DerivationRegistry;
115
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Free-standing theme-surface data types. Adapters expose render *methods*; the
3
+ * only plain data structures the surface needs are the recipe-name map and the
4
+ * (deferred) self-contained lazy-recipe shape.
5
+ */
6
+ /**
7
+ * Recipe names for one subsystem: group → variant → identity string.
8
+ * `createTheme` builds it by looping the Model and calling `adapter.recipeName`
9
+ * (not an adapter method itself). Surfaced per subsystem, e.g. `theme.colors.classes`.
10
+ */
11
+ export type NameMap = Record<string, Record<string, string>>;
12
+ /**
13
+ * The self-contained result of lazily rendering one recipe: the rule PLUS the
14
+ * variables it needs, kept SEPARATE and addressable so a delivery layer can dedup.
15
+ *
16
+ * Both halves are `{ identity, render }` — the delivery layer dedups variables by
17
+ * `path` (and recipes by `name` if the same recipe appears twice), then inserts.
18
+ * Produced by the core helper `renderRecipeStandalone`, composed from `recipeRefs`
19
+ * (core) + `renderToken` (adapter) + `renderRecipe` (adapter). Not produced yet —
20
+ * the lazy per-recipe delivery path is deferred.
21
+ */
22
+ export interface RecipeStandalone<TUnit = string> {
23
+ readonly recipe: {
24
+ readonly name: string;
25
+ readonly render: TUnit;
26
+ };
27
+ readonly variables: ReadonlyArray<{
28
+ readonly path: string;
29
+ readonly render: TUnit;
30
+ }>;
31
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Length units as a property of the value (§21).
3
+ *
4
+ * A length's unit belongs to the value, not to one global adapter switch. This module owns the whole
5
+ * length-unit story: the CSS unit set, parsing an authored length input into a canonical dimension,
6
+ * the three-layer role resolution, and the format-neutral **Model pass** that bakes a fully-resolved
7
+ * `{ value, unit }` onto every length leaf. Adapters downstream only stringify (`value + unit`).
8
+ *
9
+ * Resolution order for a length leaf's unit (most-specific wins):
10
+ * ① value-level unit — the token pinned `"1px"`; trusted verbatim, never converted
11
+ * ② role default — `units["<sub>.<prop>"]` → `units["<sub>"]` → `units.default`
12
+ * ③ built-in seed — length subsystems seed `px`; `lineHeight` → none; `letterSpacing` → em
13
+ *
14
+ * A bare number is **deferred** (px-intended magnitude, unit resolved by ②/③); `rem` is the one unit
15
+ * whose resolution divides (`value ÷ baseFontSize`). An explicit unit is **pinned** — passed through.
16
+ * Functions / keywords (`calc(…)`, `clamp(…)`, `var(…)`, `none`) are raw-string escapes, never parsed.
17
+ */
18
+ import type { Ref, ThemeModel } from "./model";
19
+ /**
20
+ * The CSS length / relative unit set we parse + validate (§21 D2 — support every unit, exclude only
21
+ * functions). A `<number><unit>` whose suffix is here becomes a pinned dimension; an unknown suffix is
22
+ * an authoring error; anything with parens / whitespace / no digits is a raw-string escape.
23
+ */
24
+ export declare const CSS_UNITS: readonly ["px", "cm", "mm", "q", "in", "pc", "pt", "rem", "em", "ex", "cap", "ch", "ic", "lh", "rlh", "%", "vw", "vh", "vi", "vb", "vmin", "vmax", "svw", "svh", "lvw", "lvh", "dvw", "dvh", "cqw", "cqh", "cqi", "cqb", "cqmin", "cqmax"];
25
+ export type Unit = (typeof CSS_UNITS)[number];
26
+ /** A resolved role: a concrete unit, or `"none"` (a length leaf that stays unit-less, e.g. lineHeight). */
27
+ export type RoleUnit = Unit | "none";
28
+ /** A parsed length: a magnitude + optional unit (absent ⇒ deferred), or a raw escape string. */
29
+ export type ParsedLength = {
30
+ value: number;
31
+ unit?: Unit;
32
+ } | {
33
+ raw: string;
34
+ };
35
+ /**
36
+ * Parse one authored length input into a {@link ParsedLength}. A number, or a bare numeric string, is
37
+ * deferred (`{ value }`). A `<number><unit>` with a known CSS unit is pinned (`{ value, unit }`). A
38
+ * `<number><unknown>` is an authoring error (a typo — real functions/keywords carry parens or letters
39
+ * with no leading number and fall through to `{ raw }`).
40
+ */
41
+ export declare const parseLength: (input: number | string) => ParsedLength;
42
+ /**
43
+ * The build/theme-level unit config (§21 D3). Keys are token-path prefixes — `units.default` (global),
44
+ * `units["<subsystem>"]` (subsystem grain), `units["<subsystem>.<property>"]` (property grain). Values
45
+ * are a concrete unit or `"none"`. Most-specific wins; falls back to the built-in seed, then `px`.
46
+ */
47
+ export type UnitsConfig = {
48
+ default?: RoleUnit;
49
+ } & Record<string, RoleUnit | undefined>;
50
+ export type UnitResolutionConfig = {
51
+ units?: UnitsConfig;
52
+ /** Divisor for a deferred magnitude resolving to `rem` (matches typography + media). Default `16`. */
53
+ baseFontSize?: number;
54
+ };
55
+ export declare const DEFAULT_BASE_FONT_SIZE = 16;
56
+ /**
57
+ * Resolve the role unit for a length leaf at `pathKey` (`"typography.letterSpacing"`). Precedence is
58
+ * by **grain**, most-specific first, with the built-in property-grain seed slotted at its own grain —
59
+ * so `SEED["typography.lineHeight"] = "none"` beats a blunt subsystem-grain `units.typography = "rem"`
60
+ * (§21 D1: property grain beats subsystem grain), while a user can still force it with the property key.
61
+ */
62
+ export declare const resolveRoleUnit: (pathKey: string, units: UnitsConfig | undefined) => RoleUnit;
63
+ /**
64
+ * Resolve one length leaf {@link Ref} against its role. A pure token reference (`{ ref }`, no baked
65
+ * value) is left untouched (its unit resolves at its own address). A **derived** length leaf
66
+ * (`{ ref, fn, arg, value }` — §10.6 scale steps) carries its own baked `value`, emitted as a literal,
67
+ * so its unit IS resolved here. A numeric or numeric-string value is deferred → resolved via the role;
68
+ * a `<number><unit>` string is pinned → carried verbatim onto `{ value, unit }`; a raw escape
69
+ * (`calc()`, keyword) is left as its string value. Returns a NEW ref when anything changed, else the
70
+ * original (structural sharing preserved for byte-identical untouched branches).
71
+ */
72
+ export declare const resolveLengthRef: (ref: Ref, role: RoleUnit, baseFontSize: number) => Ref;
73
+ /** A shadow geometry field after §21 widening — a deferred magnitude or a pinned `{ value, unit }`. */
74
+ export type ShadowDimension = number | {
75
+ value: number;
76
+ unit: Unit;
77
+ };
78
+ /** How a length-bearing property carries its value: a scalar leaf, or the shadow struct geometry. */
79
+ export type LengthKind = "length" | "shadow";
80
+ /**
81
+ * The length-field declaration (formalizes the scattered `PX_*_KEYS` of §16). Which `<subsystem>.<property>`
82
+ * tokens are length-valued, and how they carry it. Anything absent (opacity, zIndex, aspectRatio, easing,
83
+ * durations) is left untouched by the resolver. `lineHeight` is a length that seeds to `none` — listed so a
84
+ * theme CAN opt it into a unit, resolving to unit-less by default.
85
+ */
86
+ export declare const LENGTH_REGISTRY: Record<string, Record<string, LengthKind>>;
87
+ /**
88
+ * Resolve every length leaf in the {@link ThemeModel} to a concrete `{ value, unit }` (§21 D3). Pure and
89
+ * **reference-preserving**: a subsystem / property / leaf that gains no unit keeps its object identity, so
90
+ * the default px path stays byte-identical AND `override`'s structural sharing survives the re-run
91
+ * (already-resolved parent branches are returned as-is). The single point where `units` is consulted.
92
+ */
93
+ export declare const resolveModelUnits: (model: ThemeModel, config?: UnitResolutionConfig) => ThemeModel;