@animus-ui/system 0.1.0-next.9 → 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 (72) hide show
  1. package/README.md +148 -0
  2. package/dist/Animus.d.ts +36 -30
  3. package/dist/Animus.d.ts.map +1 -1
  4. package/dist/AnimusExtended.d.ts +32 -27
  5. package/dist/AnimusExtended.d.ts.map +1 -1
  6. package/dist/SystemBuilder.d.ts +38 -17
  7. package/dist/SystemBuilder.d.ts.map +1 -1
  8. package/dist/compose.d.ts +24 -0
  9. package/dist/compose.d.ts.map +1 -0
  10. package/dist/compose.js +36 -0
  11. package/dist/composeWithContext.d.ts +30 -0
  12. package/dist/composeWithContext.d.ts.map +1 -0
  13. package/dist/composeWithContext.js +79 -0
  14. package/dist/createComposedFamily-BsyI6rBc.js +230 -0
  15. package/dist/groups/index.d.ts +455 -359
  16. package/dist/groups/index.d.ts.map +1 -1
  17. package/dist/groups/index.js +139 -55
  18. package/dist/index.d.ts +11 -7
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +666 -395
  21. package/dist/keyframes.d.ts +45 -0
  22. package/dist/keyframes.d.ts.map +1 -0
  23. package/dist/runtime/createClassResolver.d.ts +10 -0
  24. package/dist/runtime/createClassResolver.d.ts.map +1 -0
  25. package/dist/runtime/createComposedFamily.d.ts +16 -0
  26. package/dist/runtime/createComposedFamily.d.ts.map +1 -0
  27. package/dist/runtime/index.d.ts +2 -18
  28. package/dist/runtime/index.d.ts.map +1 -1
  29. package/dist/runtime/resolveClasses.d.ts +59 -0
  30. package/dist/runtime/resolveClasses.d.ts.map +1 -0
  31. package/dist/runtime-entry.d.ts +10 -0
  32. package/dist/runtime-entry.d.ts.map +1 -0
  33. package/dist/runtime-entry.js +2 -0
  34. package/dist/selectors.d.ts +40 -0
  35. package/dist/selectors.d.ts.map +1 -0
  36. package/dist/size-CusLCguT.js +97 -0
  37. package/dist/theme/createTheme.d.ts +54 -54
  38. package/dist/theme/createTheme.d.ts.map +1 -1
  39. package/dist/theme/index.d.ts +0 -2
  40. package/dist/theme/index.d.ts.map +1 -1
  41. package/dist/theme/serializeTokens.d.ts +0 -14
  42. package/dist/theme/serializeTokens.d.ts.map +1 -1
  43. package/dist/theme/utils.d.ts +20 -4
  44. package/dist/theme/utils.d.ts.map +1 -1
  45. package/dist/transforms/border.d.ts +4 -0
  46. package/dist/transforms/border.d.ts.map +1 -1
  47. package/dist/transforms/createTransform.d.ts +3 -1
  48. package/dist/transforms/createTransform.d.ts.map +1 -1
  49. package/dist/transforms/grid.d.ts +12 -2
  50. package/dist/transforms/grid.d.ts.map +1 -1
  51. package/dist/transforms/index.d.ts +0 -1
  52. package/dist/transforms/index.d.ts.map +1 -1
  53. package/dist/transforms/size.d.ts +8 -0
  54. package/dist/transforms/size.d.ts.map +1 -1
  55. package/dist/types/component.d.ts +141 -23
  56. package/dist/types/component.d.ts.map +1 -1
  57. package/dist/types/config.d.ts +50 -5
  58. package/dist/types/config.d.ts.map +1 -1
  59. package/dist/types/properties.d.ts +1 -2
  60. package/dist/types/properties.d.ts.map +1 -1
  61. package/dist/types/props.d.ts +13 -25
  62. package/dist/types/props.d.ts.map +1 -1
  63. package/dist/types/theme.d.ts +73 -11
  64. package/dist/types/theme.d.ts.map +1 -1
  65. package/dist/utils/deepMerge.d.ts +5 -0
  66. package/dist/utils/deepMerge.d.ts.map +1 -0
  67. package/package.json +47 -15
  68. package/dist/PropertyBuilder.d.ts +0 -11
  69. package/dist/PropertyBuilder.d.ts.map +0 -1
  70. package/dist/size-Dge_rsuz.js +0 -70
  71. package/dist/transforms/utils.d.ts +0 -3
  72. package/dist/transforms/utils.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,204 +1,18 @@
1
- import { a as borderShorthand, c as numericOrStringScale, i as gridItemRatio, l as numericScale, n as size, o as createTransform, r as gridItem, s as createScale, t as percentageOrAbsolute, u as stringScale } from "./size-Dge_rsuz.js";
2
- import { createElement, forwardRef, useRef } from "react";
3
- //#region src/runtime/index.ts
1
+ import { n as createClassResolver, r as createComponent, t as createComposedFamily } from "./createComposedFamily-BsyI6rBc.js";
2
+ import { compose } from "./compose.js";
3
+ import { a as borderShorthand, c as numericOrStringScale, i as gridItemRatio, l as numericScale, n as size, o as createTransform, r as gridItem, s as createScale, t as percentageOrAbsolute, u as stringScale } from "./size-CusLCguT.js";
4
+ //#region src/utils/deepMerge.ts
4
5
  /**
5
- * CSS properties that accept unitless numeric values.
6
- * Bare numerics on properties NOT in this set receive `px`.
7
- * Matches @emotion/unitless and React DOM's style handling.
8
- */
9
- const UNITLESS_PROPERTIES = new Set([
10
- "animation-iteration-count",
11
- "border-image-outset",
12
- "border-image-slice",
13
- "border-image-width",
14
- "box-flex",
15
- "box-flex-group",
16
- "box-ordinal-group",
17
- "column-count",
18
- "columns",
19
- "flex",
20
- "flex-grow",
21
- "flex-positive",
22
- "flex-shrink",
23
- "flex-negative",
24
- "flex-order",
25
- "font-weight",
26
- "grid-area",
27
- "grid-column",
28
- "grid-column-end",
29
- "grid-column-span",
30
- "grid-column-start",
31
- "grid-row",
32
- "grid-row-end",
33
- "grid-row-span",
34
- "grid-row-start",
35
- "line-clamp",
36
- "line-height",
37
- "opacity",
38
- "order",
39
- "orphans",
40
- "tab-size",
41
- "widows",
42
- "z-index",
43
- "zoom",
44
- "fill-opacity",
45
- "flood-opacity",
46
- "stop-opacity",
47
- "stroke-dasharray",
48
- "stroke-dashoffset",
49
- "stroke-miterlimit",
50
- "stroke-opacity",
51
- "stroke-width"
52
- ]);
53
- /**
54
- * Apply unit fallback to a value for a given CSS property.
55
- * Unitless numeric values on properties that expect length units receive `px`.
56
- */
57
- function applyUnitFallback(value, cssProperty) {
58
- if (typeof value === "number") {
59
- if (UNITLESS_PROPERTIES.has(cssProperty)) return String(value);
60
- return `${value}px`;
61
- }
62
- return String(value);
63
- }
64
- /**
65
- * Serialize a system prop value to a lookup key matching the Rust
66
- * css_generator's serialize_value_key output format:
67
- * - Numbers and strings → their string representation
68
- * - Responsive objects → sorted "key:value" pairs joined by "|"
69
- */
70
- function serializeValueKey(value) {
71
- if (typeof value === "number" || typeof value === "string") return String(value);
72
- if (typeof value === "object" && value !== null && !Array.isArray(value)) return Object.keys(value).sort().map((k) => `${k}:${value[k]}`).join("|");
73
- return String(value);
74
- }
75
- /**
76
- * Resolve a dynamic prop value through scale lookup → transform → unit fallback.
77
- * Scale lookup uses pre-resolved values shipped from the extraction pipeline.
78
- */
79
- function resolveValue(value, dc) {
80
- const key = String(value);
81
- const scaleResolved = dc.scaleValues?.[key];
82
- if (scaleResolved != null) {
83
- const transformed = dc.transform ? dc.transform(scaleResolved) : scaleResolved;
84
- return String(transformed);
85
- }
86
- return applyUnitFallback(dc.transform ? dc.transform(value) : value, dc.varName);
87
- }
88
- /**
89
- * Create a lightweight component that applies extracted CSS class names.
90
- * Replaces Emotion's styled() for extracted components.
91
- *
92
- * The element parameter accepts either an HTML tag string (e.g. 'button') or
93
- * a React component reference (e.g. NextLink). When a component reference is
94
- * used, prop forwarding skips the HTML-attribute validity check — all
95
- * non-filtered props are forwarded to the component.
96
- *
97
- * The optional systemPropMap parameter provides the shared prop→value→className
98
- * lookup table, served as a virtual module by the Vite plugin.
99
- *
100
- * The optional dynamicPropConfig parameter provides CSS variable fallback
101
- * metadata for props with detected dynamic usage.
6
+ * Deep merge utility replaces lodash.merge for variant accumulation.
102
7
  */
103
- function createComponent(element, className, config, systemPropMap, dynamicPropConfig) {
104
- const variantProps = config.variants ? Object.keys(config.variants) : [];
105
- const stateProps = config.states || [];
106
- const systemPropNames = config.systemPropNames || [];
107
- const filterProps = new Set([
108
- ...variantProps,
109
- ...stateProps,
110
- ...systemPropNames
111
- ]);
112
- const isComponentElement = typeof element !== "string";
113
- const Component = forwardRef((props, ref) => {
114
- const classes = [className];
115
- const prevDynKey = useRef("");
116
- const prevDynStyle = useRef(null);
117
- if (config.variants) for (const [prop, vc] of Object.entries(config.variants)) {
118
- const value = props[prop] ?? vc.default;
119
- if (value != null) classes.push(`${className}--${prop}-${value}`);
120
- }
121
- if (config.states) {
122
- for (const state of config.states) if (props[state]) classes.push(`${className}--${state}`);
123
- }
124
- let dynKeyParts;
125
- let dynStyle;
126
- if (systemPropNames.length > 0) {
127
- const { customPropMap, customDynamicConfig } = config;
128
- for (const propName of systemPropNames) {
129
- if (!(propName in props)) continue;
130
- const propValue = props[propName];
131
- if (propValue == null) continue;
132
- const key = serializeValueKey(propValue);
133
- const cls = customPropMap?.[propName]?.[key] ?? systemPropMap?.[propName]?.[key];
134
- if (cls) classes.push(cls);
135
- else {
136
- const dc = customDynamicConfig?.[propName] ?? dynamicPropConfig?.[propName];
137
- if (dc) {
138
- if (!dynKeyParts) dynKeyParts = [];
139
- if (!dynStyle) dynStyle = {};
140
- if (typeof propValue === "object" && propValue !== null && !Array.isArray(propValue)) for (const [bp, bpVal] of Object.entries(propValue)) {
141
- if (bpVal == null) continue;
142
- if (bp === "_") {
143
- classes.push(dc.slotClass);
144
- const finalVal = resolveValue(bpVal, dc);
145
- dynStyle[dc.varName] = finalVal;
146
- dynKeyParts.push(`${dc.varName}:${finalVal}`);
147
- } else {
148
- classes.push(`${dc.slotClass}-${bp}`);
149
- const varName = `${dc.varName}-${bp}`;
150
- const finalVal = resolveValue(bpVal, dc);
151
- dynStyle[varName] = finalVal;
152
- dynKeyParts.push(`${varName}:${finalVal}`);
153
- }
154
- }
155
- else {
156
- classes.push(dc.slotClass);
157
- const finalVal = resolveValue(propValue, dc);
158
- dynStyle[dc.varName] = finalVal;
159
- dynKeyParts.push(`${dc.varName}:${finalVal}`);
160
- }
161
- }
162
- }
163
- }
164
- }
165
- if (props.className) classes.push(props.className);
166
- const domProps = {
167
- ref,
168
- className: classes.join(" ")
169
- };
170
- for (const [key, value] of Object.entries(props)) {
171
- if (key === "className") continue;
172
- if (filterProps.has(key)) continue;
173
- if (!isComponentElement) {}
174
- domProps[key] = value;
175
- }
176
- if (dynKeyParts && dynStyle) {
177
- const dynKey = dynKeyParts.join("|");
178
- if (dynKey !== prevDynKey.current) {
179
- prevDynKey.current = dynKey;
180
- prevDynStyle.current = dynStyle;
181
- }
182
- domProps.style = props.style ? {
183
- ...props.style,
184
- ...prevDynStyle.current
185
- } : prevDynStyle.current;
186
- }
187
- return createElement(element, domProps);
188
- });
189
- Component.displayName = className;
190
- return Object.assign(Component, { extend: () => {
191
- throw new Error(`Cannot extend extracted component "${className}" at runtime. Extensions must be authored in source code using the builder API (e.g. import the original component and call .extend() there) so the extraction pipeline can resolve them at build time.`);
192
- } });
193
- }
194
- //#endregion
195
- //#region src/AnimusExtended.ts
196
- function deepMerge$1(target, source) {
8
+ function deepMerge(target, source) {
197
9
  const result = { ...target };
198
- for (const key of Object.keys(source)) if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key]) && target[key] && typeof target[key] === "object" && !Array.isArray(target[key])) result[key] = deepMerge$1(target[key], source[key]);
10
+ for (const key of Object.keys(source)) if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key]) && target[key] && typeof target[key] === "object" && !Array.isArray(target[key])) result[key] = deepMerge(target[key], source[key]);
199
11
  else result[key] = source[key];
200
12
  return result;
201
13
  }
14
+ //#endregion
15
+ //#region src/AnimusExtended.ts
202
16
  var AnimusExtendedWithAll = class {
203
17
  propRegistry = {};
204
18
  groupRegistry = {};
@@ -207,7 +21,8 @@ var AnimusExtendedWithAll = class {
207
21
  variants = {};
208
22
  activeGroups = {};
209
23
  custom = {};
210
- constructor(props, groups, base, variants, states, activeGroups, custom) {
24
+ compounds = [];
25
+ constructor(props, groups, base, variants, states, activeGroups, custom, compounds = []) {
211
26
  this.propRegistry = props;
212
27
  this.groupRegistry = groups;
213
28
  this.baseStyles = base;
@@ -215,9 +30,10 @@ var AnimusExtendedWithAll = class {
215
30
  this.statesConfig = states;
216
31
  this.activeGroups = activeGroups;
217
32
  this.custom = custom;
33
+ this.compounds = compounds;
218
34
  }
219
35
  extend() {
220
- return new AnimusExtended(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, this.statesConfig, this.activeGroups, this.custom);
36
+ return new AnimusExtended(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, this.statesConfig, this.activeGroups, this.custom, this.compounds);
221
37
  }
222
38
  asElement(component) {
223
39
  const Component = createComponent(component, "", this._buildComponentConfig());
@@ -229,8 +45,8 @@ var AnimusExtendedWithAll = class {
229
45
  const extendFn = this.extend.bind(this);
230
46
  return Object.assign(Component, { extend: extendFn });
231
47
  }
232
- build() {
233
- return Object.assign((() => ({})), { extend: this.extend.bind(this) });
48
+ asClass() {
49
+ return createClassResolver("", this._buildComponentConfig());
234
50
  }
235
51
  _buildComponentConfig() {
236
52
  const variantConfig = {};
@@ -252,45 +68,50 @@ var AnimusExtendedWithAll = class {
252
68
  };
253
69
  var AnimusExtendedWithSystem = class extends AnimusExtendedWithAll {
254
70
  props(config) {
255
- return new AnimusExtendedWithAll(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, this.statesConfig, this.activeGroups, deepMerge$1({}, config));
71
+ return new AnimusExtendedWithAll(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, this.statesConfig, this.activeGroups, deepMerge({}, config), this.compounds);
256
72
  }
257
73
  };
258
74
  var AnimusExtendedWithStates = class extends AnimusExtendedWithSystem {
259
- groups(config) {
260
- return new AnimusExtendedWithSystem(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, this.statesConfig, deepMerge$1(this.activeGroups, config), this.custom);
75
+ system(config) {
76
+ return new AnimusExtendedWithSystem(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, this.statesConfig, deepMerge(this.activeGroups, config), this.custom, this.compounds);
261
77
  }
262
78
  };
263
- var AnimusExtendedWithVariants = class AnimusExtendedWithVariants extends AnimusExtendedWithStates {
264
- variant(options) {
265
- const prop = options.prop || "variant";
266
- return new AnimusExtendedWithVariants(this.propRegistry, this.groupRegistry, this.baseStyles, deepMerge$1(this.variants, { [prop]: options }), this.statesConfig, this.activeGroups, this.custom);
79
+ var AnimusExtendedWithCompounds = class AnimusExtendedWithCompounds extends AnimusExtendedWithStates {
80
+ compound(condition, styles) {
81
+ return new AnimusExtendedWithCompounds(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, this.statesConfig, this.activeGroups, this.custom, [...this.compounds, {
82
+ condition,
83
+ styles
84
+ }]);
267
85
  }
268
86
  states(config) {
269
- return new AnimusExtendedWithStates(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, deepMerge$1(this.statesConfig, config), this.activeGroups, this.custom);
87
+ return new AnimusExtendedWithStates(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, deepMerge(this.statesConfig, config), this.activeGroups, this.custom, this.compounds);
88
+ }
89
+ };
90
+ var AnimusExtendedWithVariants = class AnimusExtendedWithVariants extends AnimusExtendedWithCompounds {
91
+ compound(condition, styles) {
92
+ return new AnimusExtendedWithCompounds(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, this.statesConfig, this.activeGroups, this.custom, [...this.compounds, {
93
+ condition,
94
+ styles
95
+ }]);
96
+ }
97
+ variant(options) {
98
+ const prop = options.prop || "variant";
99
+ return new AnimusExtendedWithVariants(this.propRegistry, this.groupRegistry, this.baseStyles, deepMerge(this.variants, { [prop]: options }), this.statesConfig, this.activeGroups, this.custom, this.compounds);
270
100
  }
271
101
  };
272
102
  var AnimusExtendedWithBase = class extends AnimusExtendedWithVariants {
273
103
  variant(options) {
274
104
  const prop = options.prop || "variant";
275
- return new AnimusExtendedWithVariants(this.propRegistry, this.groupRegistry, this.baseStyles, deepMerge$1(this.variants, { [prop]: options }), this.statesConfig, this.activeGroups, this.custom);
105
+ return new AnimusExtendedWithVariants(this.propRegistry, this.groupRegistry, this.baseStyles, deepMerge(this.variants, { [prop]: options }), this.statesConfig, this.activeGroups, this.custom, this.compounds);
276
106
  }
277
107
  };
278
108
  var AnimusExtended = class extends AnimusExtendedWithBase {
279
109
  styles(config) {
280
- return new AnimusExtendedWithBase(this.propRegistry, this.groupRegistry, deepMerge$1(this.baseStyles, config), this.variants, this.statesConfig, this.activeGroups, this.custom);
110
+ return new AnimusExtendedWithBase(this.propRegistry, this.groupRegistry, deepMerge(this.baseStyles, config), this.variants, this.statesConfig, this.activeGroups, this.custom, this.compounds);
281
111
  }
282
112
  };
283
113
  //#endregion
284
114
  //#region src/Animus.ts
285
- /**
286
- * Deep merge utility — replaces lodash.merge for variant accumulation.
287
- */
288
- function deepMerge(target, source) {
289
- const result = { ...target };
290
- for (const key of Object.keys(source)) if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key]) && target[key] && typeof target[key] === "object" && !Array.isArray(target[key])) result[key] = deepMerge(target[key], source[key]);
291
- else result[key] = source[key];
292
- return result;
293
- }
294
115
  var AnimusWithAll = class {
295
116
  propRegistry = {};
296
117
  groupRegistry = {};
@@ -299,7 +120,8 @@ var AnimusWithAll = class {
299
120
  variants = {};
300
121
  activeGroups = {};
301
122
  custom = {};
302
- constructor(props, groups, base, variants, states, activeGroups, custom) {
123
+ compounds = [];
124
+ constructor(props, groups, base, variants, states, activeGroups, custom, compounds = []) {
303
125
  this.propRegistry = props;
304
126
  this.groupRegistry = groups;
305
127
  this.baseStyles = base;
@@ -307,9 +129,10 @@ var AnimusWithAll = class {
307
129
  this.statesConfig = states;
308
130
  this.activeGroups = activeGroups;
309
131
  this.custom = custom;
132
+ this.compounds = compounds;
310
133
  }
311
134
  extend() {
312
- return new AnimusExtended(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, this.statesConfig, this.activeGroups, this.custom);
135
+ return new AnimusExtended(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, this.statesConfig, this.activeGroups, this.custom, this.compounds);
313
136
  }
314
137
  asElement(component) {
315
138
  const Component = createComponent(component, "", this._buildComponentConfig());
@@ -321,8 +144,8 @@ var AnimusWithAll = class {
321
144
  const extendFn = this.extend.bind(this);
322
145
  return Object.assign(Component, { extend: extendFn });
323
146
  }
324
- build() {
325
- return Object.assign((() => ({})), { extend: this.extend.bind(this) });
147
+ asClass() {
148
+ return createClassResolver("", this._buildComponentConfig());
326
149
  }
327
150
  _buildComponentConfig() {
328
151
  const variantConfig = {};
@@ -343,31 +166,48 @@ var AnimusWithAll = class {
343
166
  }
344
167
  };
345
168
  var AnimusWithSystem = class extends AnimusWithAll {
346
- constructor(props, groups, base, variants, states, activeGroups) {
347
- super(props, groups, base, variants, states, activeGroups, {});
169
+ constructor(props, groups, base, variants, states, activeGroups, compounds = []) {
170
+ super(props, groups, base, variants, states, activeGroups, {}, compounds);
348
171
  }
349
172
  props(config) {
350
- return new AnimusWithAll(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, this.statesConfig, this.activeGroups, config);
173
+ return new AnimusWithAll(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, this.statesConfig, this.activeGroups, config, this.compounds);
351
174
  }
352
175
  };
353
176
  var AnimusWithStates = class extends AnimusWithSystem {
354
- constructor(props, groups, base, variants, states) {
355
- super(props, groups, base, variants, states, {});
177
+ constructor(props, groups, base, variants, states, compounds = []) {
178
+ super(props, groups, base, variants, states, {}, compounds);
356
179
  }
357
- groups(config) {
358
- return new AnimusWithSystem(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, this.statesConfig, config);
180
+ system(config) {
181
+ return new AnimusWithSystem(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, this.statesConfig, config, this.compounds);
359
182
  }
360
183
  };
361
- var AnimusWithVariants = class AnimusWithVariants extends AnimusWithStates {
362
- constructor(props, groups, base, variants) {
363
- super(props, groups, base, variants, {});
184
+ var AnimusWithCompounds = class AnimusWithCompounds extends AnimusWithStates {
185
+ constructor(props, groups, base, variants, compounds = []) {
186
+ super(props, groups, base, variants, {}, compounds);
187
+ }
188
+ compound(condition, styles) {
189
+ return new AnimusWithCompounds(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, [...this.compounds, {
190
+ condition,
191
+ styles
192
+ }]);
364
193
  }
365
194
  states(config) {
366
- return new AnimusWithStates(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, config);
195
+ return new AnimusWithStates(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, config, this.compounds);
196
+ }
197
+ };
198
+ var AnimusWithVariants = class AnimusWithVariants extends AnimusWithCompounds {
199
+ constructor(props, groups, base, variants, compounds = []) {
200
+ super(props, groups, base, variants, compounds);
201
+ }
202
+ compound(condition, styles) {
203
+ return new AnimusWithCompounds(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, [...this.compounds, {
204
+ condition,
205
+ styles
206
+ }]);
367
207
  }
368
208
  variant(options) {
369
209
  const prop = options.prop || "variant";
370
- return new AnimusWithVariants(this.propRegistry, this.groupRegistry, this.baseStyles, deepMerge(this.variants, { [prop]: options }));
210
+ return new AnimusWithVariants(this.propRegistry, this.groupRegistry, this.baseStyles, deepMerge(this.variants, { [prop]: options }), this.compounds);
371
211
  }
372
212
  };
373
213
  var AnimusWithBase = class extends AnimusWithVariants {
@@ -376,7 +216,7 @@ var AnimusWithBase = class extends AnimusWithVariants {
376
216
  }
377
217
  variant(options) {
378
218
  const prop = options.prop || "variant";
379
- return new AnimusWithVariants(this.propRegistry, this.groupRegistry, this.baseStyles, deepMerge(this.variants, { [prop]: options }));
219
+ return new AnimusWithVariants(this.propRegistry, this.groupRegistry, this.baseStyles, deepMerge(this.variants, { [prop]: options }), this.compounds);
380
220
  }
381
221
  };
382
222
  var Animus = class extends AnimusWithBase {
@@ -388,64 +228,275 @@ var Animus = class extends AnimusWithBase {
388
228
  }
389
229
  };
390
230
  //#endregion
391
- //#region src/PropertyBuilder.ts
392
- var PropertyBuilder = class PropertyBuilder {
393
- #props;
394
- #groups;
395
- constructor(props, groups) {
396
- this.#props = props || {};
397
- this.#groups = groups || {};
231
+ //#region src/keyframes.ts
232
+ const fnv1a = (input) => {
233
+ let hash = 2166136261;
234
+ for (let i = 0; i < input.length; i++) {
235
+ hash ^= input.charCodeAt(i);
236
+ hash = hash + ((hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24)) >>> 0;
237
+ }
238
+ return hash.toString(36);
239
+ };
240
+ const serializeFrames = (frames) => {
241
+ return Object.keys(frames).sort().map((stop) => {
242
+ const frame = frames[stop] ?? {};
243
+ return `${stop}{${Object.keys(frame).sort().map((p) => `${p}:${String(frame[p])}`).join(";")}}`;
244
+ }).join("|");
245
+ };
246
+ const generateName = (frames) => `animus-kf-${fnv1a(serializeFrames(frames))}`;
247
+ const createRef = (name, resolvedName) => ({
248
+ __brand: "KeyframeRef",
249
+ __name: name,
250
+ toString() {
251
+ return resolvedName;
252
+ },
253
+ valueOf() {
254
+ return resolvedName;
255
+ }
256
+ });
257
+ function keyframes(map) {
258
+ const frameData = {};
259
+ const refs = {};
260
+ for (const key of Object.keys(map)) {
261
+ const frames = map[key];
262
+ const resolvedName = generateName(frames);
263
+ frameData[key] = {
264
+ name: resolvedName,
265
+ frames
266
+ };
267
+ refs[key] = createRef(key, resolvedName);
398
268
  }
399
- addGroup(name, config) {
400
- const newGroup = { [name]: Object.keys(config) };
401
- return new PropertyBuilder({
402
- ...this.#props,
403
- ...config
404
- }, {
405
- ...this.#groups,
406
- ...newGroup
407
- });
269
+ return {
270
+ __brand: "Keyframes",
271
+ __frames: frameData,
272
+ ...refs
273
+ };
274
+ }
275
+ //#endregion
276
+ //#region src/selectors.ts
277
+ /**
278
+ * Built-in selector aliases.
279
+ *
280
+ * Compound selectors (e.g. `_disabled`) target multiple CSS selectors
281
+ * via comma-separation to cover native, ARIA, and data attribute conventions.
282
+ */
283
+ const BUILT_IN_SELECTORS = {
284
+ _link: {
285
+ selector: "&:link",
286
+ order: 10
287
+ },
288
+ _visited: {
289
+ selector: "&:visited",
290
+ order: 20
291
+ },
292
+ _hover: {
293
+ selector: "&:hover",
294
+ order: 30
295
+ },
296
+ _focusWithin: {
297
+ selector: "&:focus-within",
298
+ order: 40
299
+ },
300
+ _focus: {
301
+ selector: "&:focus",
302
+ order: 50
303
+ },
304
+ _focusVisible: {
305
+ selector: "&:focus-visible",
306
+ order: 60
307
+ },
308
+ _active: {
309
+ selector: "&:active",
310
+ order: 70
311
+ },
312
+ _target: {
313
+ selector: "&:target",
314
+ order: 80
315
+ },
316
+ _checked: {
317
+ selector: "&:checked, &[aria-checked=\"true\"], &[data-checked]",
318
+ order: 100
319
+ },
320
+ _invalid: {
321
+ selector: "&:invalid, &[aria-invalid=\"true\"], &[data-invalid]",
322
+ order: 110
323
+ },
324
+ _required: {
325
+ selector: "&:required, &[aria-required=\"true\"]",
326
+ order: 120
327
+ },
328
+ _readOnly: {
329
+ selector: "&:read-only, &[aria-readonly=\"true\"], &[data-readonly]",
330
+ order: 130
331
+ },
332
+ _expanded: {
333
+ selector: "&[aria-expanded=\"true\"], &[data-expanded]",
334
+ order: 140
335
+ },
336
+ _selected: {
337
+ selector: "&[aria-selected=\"true\"], &[data-selected]",
338
+ order: 150
339
+ },
340
+ _pressed: {
341
+ selector: "&[aria-pressed=\"true\"], &[data-pressed]",
342
+ order: 160
343
+ },
344
+ _disabled: {
345
+ selector: "&:disabled, &[disabled], &[aria-disabled=\"true\"], &[data-disabled]",
346
+ order: 200
347
+ },
348
+ _before: {
349
+ selector: "&::before",
350
+ order: 300
351
+ },
352
+ _after: {
353
+ selector: "&::after",
354
+ order: 310
355
+ },
356
+ _placeholder: {
357
+ selector: "&::placeholder",
358
+ order: 320
359
+ },
360
+ _selection: {
361
+ selector: "&::selection",
362
+ order: 330
363
+ },
364
+ _first: {
365
+ selector: "&:first-child",
366
+ order: 400
367
+ },
368
+ _last: {
369
+ selector: "&:last-child",
370
+ order: 410
371
+ },
372
+ _even: {
373
+ selector: "&:nth-child(even)",
374
+ order: 420
375
+ },
376
+ _odd: {
377
+ selector: "&:nth-child(odd)",
378
+ order: 430
379
+ },
380
+ _empty: {
381
+ selector: "&:empty",
382
+ order: 440
408
383
  }
409
- build() {
410
- return {
411
- propRegistry: this.#props,
412
- groupRegistry: this.#groups
384
+ };
385
+ /**
386
+ * Merge user-provided selectors with built-in defaults.
387
+ * User selectors override built-in aliases of the same name.
388
+ * New aliases get an order value based on their position (500+).
389
+ */
390
+ function mergeSelectors(base, custom) {
391
+ const merged = { ...base };
392
+ let nextOrder = 500;
393
+ for (const [key, selector] of Object.entries(custom)) if (key in merged) merged[key] = {
394
+ selector,
395
+ order: merged[key].order
396
+ };
397
+ else {
398
+ merged[key] = {
399
+ selector,
400
+ order: nextOrder
413
401
  };
402
+ nextOrder += 10;
414
403
  }
415
- };
404
+ return merged;
405
+ }
406
+ /** Get the sorted alias keys for deterministic cascade ordering. */
407
+ function getSortedAliasKeys(map) {
408
+ return Object.keys(map).sort((a, b) => map[a].order - map[b].order);
409
+ }
410
+ /**
411
+ * Serialize the selector map for the extraction pipeline.
412
+ * Emits a flat `Record<string, string>` (alias → selector) plus
413
+ * the ordered key list for cascade determinism.
414
+ */
415
+ function serializeSelectorMap(map) {
416
+ const selectors = {};
417
+ const order = getSortedAliasKeys(map);
418
+ for (const key of order) selectors[key] = map[key].selector;
419
+ return {
420
+ selectors,
421
+ order
422
+ };
423
+ }
416
424
  //#endregion
417
425
  //#region src/SystemBuilder.ts
418
426
  var SystemBuilder = class SystemBuilder {
419
427
  #propRegistry;
420
428
  #groupRegistry;
421
- #globalStyles;
422
- constructor(propRegistry, groupRegistry, globalStyles) {
429
+ #selectorRegistry;
430
+ #includesRegistry;
431
+ constructor(propRegistry, groupRegistry, selectorRegistry, includesRegistry) {
423
432
  this.#propRegistry = propRegistry || {};
424
433
  this.#groupRegistry = groupRegistry || {};
425
- this.#globalStyles = globalStyles;
434
+ this.#selectorRegistry = selectorRegistry || { ...BUILT_IN_SELECTORS };
435
+ this.#includesRegistry = includesRegistry || [];
426
436
  }
427
- withProperties(cb) {
428
- const result = cb(new PropertyBuilder());
429
- return new SystemBuilder(result.propRegistry, result.groupRegistry, this.#globalStyles);
437
+ addSelectors(selectors) {
438
+ const merged = mergeSelectors(this.#selectorRegistry, selectors);
439
+ return new SystemBuilder(this.#propRegistry, this.#groupRegistry, merged, this.#includesRegistry);
430
440
  }
431
- withGlobalStyles(styles) {
432
- return new SystemBuilder(this.#propRegistry, this.#groupRegistry, styles);
441
+ addGroup(name, config) {
442
+ if (name in this.#propRegistry) throw new Error(`Group name "${name}" collides with an existing prop name. Group names and prop names must be disjoint.`);
443
+ for (const key of Object.keys(config)) if (key in this.#propRegistry) {
444
+ const existing = this.#propRegistry[key];
445
+ const incoming = config[key];
446
+ if (existing.property !== incoming.property || existing.scale !== incoming.scale || existing.transform !== incoming.transform || existing.negative !== incoming.negative) throw new Error(`Prop "${key}" already registered with a different definition. Existing: property="${existing.property}", scale="${String(existing.scale)}". Incoming: property="${incoming.property}", scale="${String(incoming.scale)}".`);
447
+ }
448
+ const nextProps = {
449
+ ...this.#propRegistry,
450
+ ...config
451
+ };
452
+ const newGroup = { [name]: Object.keys(config) };
453
+ return new SystemBuilder(nextProps, {
454
+ ...this.#groupRegistry,
455
+ ...newGroup
456
+ }, this.#selectorRegistry, this.#includesRegistry);
457
+ }
458
+ addProps(config) {
459
+ for (const key of Object.keys(config)) if (key in this.#groupRegistry) throw new Error(`Prop name "${key}" collides with an existing group name. Group names and prop names must be disjoint.`);
460
+ for (const key of Object.keys(config)) if (key in this.#propRegistry) {
461
+ const existing = this.#propRegistry[key];
462
+ const incoming = config[key];
463
+ if (existing.property !== incoming.property || existing.scale !== incoming.scale || existing.transform !== incoming.transform || existing.negative !== incoming.negative) throw new Error(`Prop "${key}" already registered with a different definition.`);
464
+ }
465
+ return new SystemBuilder({
466
+ ...this.#propRegistry,
467
+ ...config
468
+ }, this.#groupRegistry, this.#selectorRegistry, this.#includesRegistry);
433
469
  }
434
470
  build() {
435
471
  const animus = new Animus(this.#propRegistry, this.#groupRegistry);
436
- const globalStyles = this.#globalStyles;
437
- return Object.assign(animus, { serialize: () => {
438
- return serializeInstance(this.#propRegistry, this.#groupRegistry, globalStyles);
472
+ const propRegistry = this.#propRegistry;
473
+ const groupRegistry = this.#groupRegistry;
474
+ const selectorRegistry = this.#selectorRegistry;
475
+ const system = Object.assign(animus, { toConfig: () => {
476
+ return serializeInstance(propRegistry, groupRegistry, selectorRegistry);
439
477
  } });
478
+ const createGlobalStyles = ((styles) => ({
479
+ __brand: "GlobalStyleBlock",
480
+ styles
481
+ }));
482
+ const createKeyframes = ((frames) => keyframes(frames));
483
+ return {
484
+ system,
485
+ createGlobalStyles,
486
+ createKeyframes
487
+ };
440
488
  }
441
489
  };
442
- function serializeInstance(propRegistry, groupRegistry, globalStyles) {
490
+ function serializeInstance(propRegistry, groupRegistry, selectorRegistry) {
443
491
  const serialized = {};
444
492
  const transforms = {};
445
493
  for (const [propName, entry] of Object.entries(propRegistry)) {
446
494
  const s = { property: entry.property };
447
495
  if (entry.properties && entry.properties.length > 0) s.properties = [...entry.properties];
448
- if (typeof entry.scale === "string") s.scale = entry.scale;
496
+ const scale = entry.scale;
497
+ if (typeof scale === "string") s.scale = scale;
498
+ else if (scale && typeof scale === "object") s.scale = scale;
499
+ if (entry.negative) s.negative = true;
449
500
  if (entry.transform) {
450
501
  const fn = entry.transform;
451
502
  const name = fn.transformName ?? fn.name;
@@ -454,18 +505,20 @@ function serializeInstance(propRegistry, groupRegistry, globalStyles) {
454
505
  transforms[name] = fn;
455
506
  }
456
507
  }
508
+ if (entry.currentVar) s.currentVar = entry.currentVar;
457
509
  serialized[propName] = s;
458
510
  }
459
- const result = {
511
+ const { selectors, order } = serializeSelectorMap(selectorRegistry);
512
+ return {
460
513
  propConfig: JSON.stringify(serialized),
461
514
  groupRegistry: JSON.stringify(groupRegistry),
462
- transforms
515
+ transforms,
516
+ selectorAliases: JSON.stringify(selectors),
517
+ selectorOrder: JSON.stringify(order)
463
518
  };
464
- if (globalStyles) result.globalStyles = globalStyles;
465
- return result;
466
519
  }
467
- function createSystem() {
468
- return new SystemBuilder();
520
+ function createSystem(config) {
521
+ return new SystemBuilder(void 0, void 0, void 0, config?.includes ?? []);
469
522
  }
470
523
  //#endregion
471
524
  //#region src/theme/utils.ts
@@ -485,146 +538,364 @@ function merge(target, ...sources) {
485
538
  }
486
539
  return target;
487
540
  }
488
- function mapValues(obj, fn) {
541
+ /**
542
+ * Resolve a dot-path string against a nested object.
543
+ * walkDotPath({ gray: { 50: '#fafafa' } }, 'gray.50') → '#fafafa'
544
+ * The `_` identity key is handled: 'primary' resolves to obj.primary._ if obj.primary is an object with _.
545
+ */
546
+ function walkDotPath(obj, path) {
547
+ const parts = path.split(".");
548
+ let current = obj;
549
+ for (const part of parts) {
550
+ if (!isObject(current)) return void 0;
551
+ current = current[part];
552
+ }
553
+ return current;
554
+ }
555
+ /**
556
+ * Flatten a nested object into a flat Record with dot-path keys.
557
+ * The `_` key is an identity marker — it produces the parent key without suffix.
558
+ * { gray: { 50: '#fafafa' } } → { 'gray.50': '#fafafa' }
559
+ * { primary: { _: 'ember', hover: 'x' } } → { 'primary': 'ember', 'primary.hover': 'x' }
560
+ * CSS variable names use dash-join, computed at the serialization boundary (not here).
561
+ */
562
+ function flattenToDotPaths(object, path) {
489
563
  const result = {};
490
- for (const key of Object.keys(obj)) result[key] = fn(obj[key], key, obj);
564
+ for (const key of Object.keys(object)) {
565
+ const nextKey = path ? key === "_" ? path : `${path}.${key}` : key;
566
+ const current = object[key];
567
+ if (isObject(current)) Object.assign(result, flattenToDotPaths(current, nextKey));
568
+ else result[nextKey] = current;
569
+ }
491
570
  return result;
492
571
  }
493
- //#endregion
494
- //#region src/theme/flattenScale.ts
495
- function flattenScale(object, path) {
496
- return Object.keys(object).reduce((carry, key) => {
497
- const nextKey = path ? `${path}${key === "_" ? "" : `-${key}`}` : key;
498
- const current = object[key];
499
- if (isObject(current)) return {
500
- ...carry,
501
- ...flattenScale(current, nextKey)
502
- };
503
- return {
504
- ...carry,
505
- [nextKey]: object[key]
506
- };
507
- }, {});
572
+ /**
573
+ * Convert a dot-path key to a dash-join key for CSS variable naming.
574
+ * 'gray.50' 'gray-50'
575
+ * 'primary.hover' 'primary-hover'
576
+ */
577
+ function dotToDash(dotPath) {
578
+ return dotPath.replace(/\./g, "-");
508
579
  }
580
+ /** Map over object values — matches lodash.mapValues overload signatures */
509
581
  //#endregion
510
- //#region src/theme/serializeTokens.ts
511
- const templateBreakpoints = (value, alias, theme) => {
512
- if (isObject(value)) {
513
- const { _, ...rest } = value;
514
- const css = { [alias]: _ };
515
- if (theme) {
516
- const breakpoints = theme.breakpoints;
517
- Object.keys(breakpoints).forEach((key) => {
518
- if (rest[key]) css[breakpoints[key]] = { [alias]: rest[key] };
519
- });
520
- }
521
- return css;
582
+ //#region src/theme/createTheme.ts
583
+ const COLOR_FUNCTION_PREFIXES = [
584
+ "rgb(",
585
+ "rgba(",
586
+ "hsl(",
587
+ "hsla(",
588
+ "oklch(",
589
+ "oklab(",
590
+ "lch(",
591
+ "lab(",
592
+ "color(",
593
+ "color-mix("
594
+ ];
595
+ /** Validate that a value is a valid CSS <color>. */
596
+ function isValidCSSColor(value) {
597
+ if (typeof value !== "string") return false;
598
+ const v = value.trim();
599
+ if (v === "") return false;
600
+ if (v === "transparent" || v === "currentColor" || v === "currentcolor") return true;
601
+ if (v.startsWith("#") && /^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(v)) return true;
602
+ for (const prefix of COLOR_FUNCTION_PREFIXES) if (v.startsWith(prefix) && v.endsWith(")")) return true;
603
+ if (/^[a-zA-Z]+$/.test(v)) return true;
604
+ return false;
605
+ }
606
+ /**
607
+ * Validate that mode aliases reference existing color keys via dot-path traversal.
608
+ * Aliases are dot-path strings like 'gray.50' that must resolve in the nested color structure.
609
+ */
610
+ function validateModeAliases(modeName, aliases, nestedColors, flatColorKeys, prefix) {
611
+ for (const [key, value] of Object.entries(aliases)) {
612
+ const aliasPath = prefix ? `${prefix}.${key}` : key;
613
+ if (key === "_") {
614
+ if (typeof value === "string") {
615
+ if (walkDotPath(nestedColors, value) === void 0) throw new Error(`addColorModes: mode '${modeName}' references unknown color '${value}' for alias '${prefix || key}'. Available colors: ${flatColorKeys.slice(0, 10).join(", ")}${flatColorKeys.length > 10 ? ", ..." : ""}`);
616
+ } else if (isObject(value)) validateModeAliases(modeName, value, nestedColors, flatColorKeys, prefix);
617
+ } else if (typeof value === "string") {
618
+ if (walkDotPath(nestedColors, value) === void 0) throw new Error(`addColorModes: mode '${modeName}' references unknown color '${value}' for alias '${aliasPath}'. Available colors: ${flatColorKeys.slice(0, 10).join(", ")}${flatColorKeys.length > 10 ? ", ..." : ""}`);
619
+ } else if (isObject(value)) validateModeAliases(modeName, value, nestedColors, flatColorKeys, aliasPath);
522
620
  }
523
- return { [alias]: value };
524
- };
525
- const serializeTokens = (tokens, prefix, theme) => {
526
- const tokenReferences = {};
527
- const tokenVariables = {};
528
- Object.keys(tokens).forEach((key) => {
529
- const varName = `--${prefix}-${key.replace("$", "")}`;
530
- tokenReferences[key] = `var(${varName})`;
531
- merge(tokenVariables, templateBreakpoints(tokens[key], varName, theme));
532
- });
621
+ }
622
+ /** Validate all color entries, throwing on invalid values. */
623
+ function validateColors(colors) {
624
+ for (const [key, value] of Object.entries(colors)) if (isObject(value)) validateColors(value);
625
+ else if (!isValidCSSColor(value)) throw new Error(`addColors: '${String(value)}' is not a valid CSS <color> value for key '${key}'. Expected hex (#fff), rgb(), hsl(), oklch(), named color, transparent, or currentColor.`);
626
+ }
627
+ function createState(theme) {
533
628
  return {
534
- tokens: tokenReferences,
535
- variables: tokenVariables
629
+ theme: theme || { breakpoints: {} },
630
+ emittedScales: /* @__PURE__ */ new Set(),
631
+ contextualVars: /* @__PURE__ */ new Map()
536
632
  };
537
- };
538
- //#endregion
539
- //#region src/theme/createTheme.ts
540
- var ThemeBuilder = class {
541
- #theme = {};
542
- constructor(baseTheme) {
543
- this.#theme = baseTheme;
544
- }
545
- /**
546
- *
547
- * @param key A key of the current theme to transform into CSS Variables and Variable References
548
- * @example .createScaleVariables('fontSize')
549
- */
550
- createScaleVariables(key) {
551
- const { variables, tokens } = serializeTokens(this.#theme[key], key, this.#theme);
552
- this.#theme = merge({}, this.#theme, {
553
- [key]: tokens,
554
- _variables: { [key]: variables },
555
- _tokens: { [key]: this.#theme[key] }
556
- });
557
- return this;
633
+ }
634
+ function copyState(state, nextTheme) {
635
+ const next = {
636
+ theme: nextTheme,
637
+ emittedScales: new Set(state.emittedScales),
638
+ contextualVars: /* @__PURE__ */ new Map()
639
+ };
640
+ for (const [scale, vars] of state.contextualVars) next.contextualVars.set(scale, [...vars]);
641
+ return next;
642
+ }
643
+ /**
644
+ * ThemeScales — the final phase. Has addScale, extendScale, declareContextualVars, build.
645
+ * Also allows addColors and addColorModes for augmentation.
646
+ */
647
+ var ThemeBuilder = class ThemeBuilder {
648
+ /** @internal */ _state;
649
+ constructor(state) {
650
+ this._state = state;
651
+ }
652
+ addBreakpoints(breakpoints) {
653
+ for (const [key, value] of Object.entries(breakpoints)) if (typeof value !== "number" || value < 0) throw new Error(`addBreakpoints: breakpoint '${key}' must be a non-negative number, got ${JSON.stringify(value)}`);
654
+ const nextTheme = merge({}, this._state.theme, { breakpoints });
655
+ return new ThemeBuilder(copyState(this._state, nextTheme));
656
+ }
657
+ from(builtTheme) {
658
+ const raw = {};
659
+ for (const key of Object.keys(builtTheme)) {
660
+ const val = builtTheme[key];
661
+ if (typeof val !== "function") raw[key] = val;
662
+ }
663
+ const nextTheme = merge({}, this._state.theme, raw);
664
+ const next = new ThemeBuilder(copyState(this._state, nextTheme));
665
+ const manifest = builtTheme.manifest;
666
+ if (manifest?.variableMap) for (const tokenPath of Object.keys(manifest.variableMap)) {
667
+ const scale = tokenPath.split(".")[0];
668
+ next._state.emittedScales.add(scale === "colors" ? "colors" : scale);
669
+ }
670
+ if (manifest?.contextualVars) for (const [scale, vars] of Object.entries(manifest.contextualVars)) next._state.contextualVars.set(scale, [...vars]);
671
+ return next;
558
672
  }
559
- /**
560
- *
561
- * @param colors A map of color tokens to add to the theme. These tokens are immediately converted to CSS Variables `--color-${key}`.
562
- * @example .addColors({ navy: 'navy', hyper: 'purple' })
563
- */
564
673
  addColors(colors) {
565
- const flatColors = flattenScale(colors);
566
- const { variables, tokens } = serializeTokens(flatColors, "color", this.#theme);
567
- this.#theme = merge({}, this.#theme, {
568
- colors: tokens,
569
- _variables: { root: variables },
570
- _tokens: { colors: flatColors }
571
- });
572
- return this;
674
+ validateColors(colors);
675
+ const nextTheme = merge({}, this._state.theme, { colors });
676
+ const next = new ThemeBuilder(copyState(this._state, nextTheme));
677
+ next._state.emittedScales.add("colors");
678
+ return next;
573
679
  }
574
- /**
575
- *
576
- * @param initialMode A key of the object passed for modes. This sets the default state for the theme and transforms the correct variables.
577
- * @param modes A map of color modes with keys of each possible mode with a value of alias to color keys. This must be called after `addColors`
578
- * @example .addColorModes('light', { light: { primary: 'hyper' }, { dark: { primary: 'navy' } } })
579
- */
580
680
  addColorModes(initialMode, modeConfig) {
581
- const modes = mapValues(modeConfig, (mode) => flattenScale(mode));
582
- const { tokens: colors, variables } = serializeTokens(mapValues(merge({}, this.#theme.modes?.[initialMode], modes[initialMode]), (color) => this.#theme.colors[color]), "color", this.#theme);
583
- const getColorValue = (color) => this.#theme._tokens?.colors?.[color];
584
- this.#theme = merge({}, this.#theme, {
585
- colors,
586
- modes,
587
- mode: initialMode,
588
- _getColorValue: getColorValue,
589
- _variables: { mode: variables },
590
- _tokens: { modes: mapValues(modes, (mode) => mapValues(mode, getColorValue)) }
681
+ const nestedColors = this._state.theme.colors || {};
682
+ const flatColors = flattenToDotPaths(nestedColors);
683
+ const flatColorKeys = Object.keys(flatColors);
684
+ for (const [modeName, modeAliases] of Object.entries(modeConfig)) validateModeAliases(modeName, modeAliases, nestedColors, flatColorKeys, "");
685
+ const nextTheme = merge({}, this._state.theme, {
686
+ modes: modeConfig,
687
+ mode: initialMode
591
688
  });
592
- return this;
593
- }
594
- /**
595
- *
596
- * @param key A new key of theme
597
- * @param createScale A function that accepts the current theme and returns a new object of scale values.
598
- * @example .addScale('fonts', () => ({ basic: 'Gotham', cool: 'Wingdings' }))
599
- */
600
- addScale(key, createScale) {
601
- this.#theme = merge({}, this.#theme, { [key]: flattenScale(createScale(this.#theme)) });
602
- return this;
689
+ return new ThemeBuilder(copyState(this._state, nextTheme));
690
+ }
691
+ addScale(config) {
692
+ const { name, values, emit } = config;
693
+ const nextTheme = merge({}, this._state.theme, { [name]: values });
694
+ const next = new ThemeBuilder(copyState(this._state, nextTheme));
695
+ if (emit) next._state.emittedScales.add(name);
696
+ return next;
697
+ }
698
+ declareContextualVars(vars) {
699
+ for (const scale of Object.keys(vars)) if (!(scale in this._state.theme)) throw new Error(`declareContextualVars: scale '${scale}' not found — call addColors or addScale first`);
700
+ const next = new ThemeBuilder(copyState(this._state, this._state.theme));
701
+ for (const [scale, names] of Object.entries(vars)) {
702
+ const existing = next._state.contextualVars.get(scale) || [];
703
+ next._state.contextualVars.set(scale, [...existing, ...names]);
704
+ }
705
+ return next;
603
706
  }
604
- /**
605
- *
606
- * @param key A current key of theme to be updated with new or computed values
607
- * @param updateFn A function that accepts an argument of the current values at the specified keys an returns a map of new values to merge.
608
- * @example .updateScale('fonts', ({ basic }) => ({ basicFallback: `{basic}, Montserrat` }))
609
- */
610
- updateScale(key, updateFn) {
611
- this.#theme = merge({}, this.#theme, { [key]: updateFn(this.#theme[key]) });
612
- return this;
707
+ extendScale(key, updateFn) {
708
+ const nextTheme = merge({}, this._state.theme, { [key]: updateFn(this._state.theme[key]) });
709
+ return new ThemeBuilder(copyState(this._state, nextTheme));
613
710
  }
614
711
  /**
615
- * This finalizes the theme build and returns the final theme and variables to be provided.
616
- * Simplify flattens the deeply nested MergeTheme chain into a shallow object type.
712
+ * Finalize the theme build.
713
+ * Flattens nested data at the boundary produces manifest and serialize().
617
714
  */
618
715
  build() {
619
- const { variables } = serializeTokens(mapValues(this.#theme.breakpoints, (val) => `${val}px`), "breakpoint", this.#theme);
620
- return merge({}, this.#theme, {
621
- _variables: { breakpoints: variables },
622
- _tokens: {}
716
+ const theme = merge({}, this._state.theme);
717
+ const emittedScales = this._state.emittedScales;
718
+ const contextualVars = this._state.contextualVars;
719
+ const { tokenMap, variableMap, variables, modeVariables, modeTokens } = flattenTheme(theme, emittedScales);
720
+ resolveTokenRefs(tokenMap, variableMap, emittedScales);
721
+ const bpVariables = {};
722
+ if (theme.breakpoints && isObject(theme.breakpoints)) for (const [key, value] of Object.entries(theme.breakpoints)) bpVariables[`--breakpoint-${key}`] = `${value}px`;
723
+ let contextualVarsSerialized;
724
+ if (contextualVars.size > 0) {
725
+ contextualVarsSerialized = {};
726
+ for (const [scale, vars] of contextualVars) contextualVarsSerialized[scale] = vars;
727
+ }
728
+ const variableCss = buildVariableCss(variables, bpVariables, modeVariables);
729
+ const manifest = {
730
+ tokenMap: {
731
+ ...tokenMap,
732
+ ...Object.fromEntries(Object.entries(theme.breakpoints || {}).map(([k, v]) => [`breakpoints.${k}`, String(v)]))
733
+ },
734
+ variableMap,
735
+ modes: modeTokens,
736
+ variableCss,
737
+ ...contextualVarsSerialized ? { contextualVars: contextualVarsSerialized } : {}
738
+ };
739
+ Object.defineProperty(theme, "manifest", {
740
+ value: manifest,
741
+ enumerable: false,
742
+ configurable: false,
743
+ writable: false
744
+ });
745
+ Object.defineProperty(theme, "serialize", {
746
+ value: () => ({
747
+ scalesJson: JSON.stringify(manifest.tokenMap),
748
+ variableMapJson: JSON.stringify(manifest.variableMap),
749
+ variableCss: manifest.variableCss,
750
+ contextualVarsJson: JSON.stringify(manifest.contextualVars ?? {})
751
+ }),
752
+ enumerable: false,
753
+ configurable: false,
754
+ writable: false
623
755
  });
756
+ Object.defineProperty(theme, "varRef", {
757
+ value: (tokenPath) => {
758
+ const varName = variableMap[tokenPath];
759
+ if (varName) return `var(${varName})`;
760
+ const dotIdx = tokenPath.indexOf(".");
761
+ if (dotIdx === -1) return void 0;
762
+ const scale = tokenPath.slice(0, dotIdx);
763
+ const key = tokenPath.slice(dotIdx + 1);
764
+ const scaleObj = theme[scale];
765
+ if (!isObject(scaleObj)) return void 0;
766
+ const val = walkDotPath(scaleObj, key);
767
+ return val !== void 0 ? String(val) : void 0;
768
+ },
769
+ enumerable: false,
770
+ configurable: false,
771
+ writable: false
772
+ });
773
+ return theme;
624
774
  }
625
775
  };
626
- function createTheme(base) {
627
- return new ThemeBuilder(base);
776
+ function createTheme() {
777
+ return new ThemeBuilder(createState());
778
+ }
779
+ /** Token ref pattern: {scale.key} or {scale.key.sub} */
780
+ const TOKEN_REF_RE = /\{([^}]+)\}/g;
781
+ /**
782
+ * Flatten the nested theme into dot-path keyed token map and CSS variable declarations.
783
+ * This is the ONLY place where flattening happens.
784
+ */
785
+ function flattenTheme(theme, emittedScales) {
786
+ const tokenMap = {};
787
+ const variableMap = {};
788
+ const variables = {};
789
+ const modeVariables = {};
790
+ const modeTokens = {};
791
+ for (const [scaleName, scaleValue] of Object.entries(theme)) {
792
+ if (scaleName.startsWith("_")) continue;
793
+ if (scaleName === "breakpoints" || scaleName === "mode" || scaleName === "modes") continue;
794
+ if (typeof scaleValue === "function") continue;
795
+ if (!isObject(scaleValue)) continue;
796
+ const flat = flattenToDotPaths(scaleValue);
797
+ const isEmitted = emittedScales.has(scaleName);
798
+ for (const [dotKey, rawValue] of Object.entries(flat)) {
799
+ const tokenPath = `${scaleName}.${dotKey}`;
800
+ const dashKey = dotToDash(dotKey);
801
+ const varName = `--${scaleName === "colors" ? "color" : scaleName}-${dashKey}`;
802
+ if (isEmitted) {
803
+ tokenMap[tokenPath] = `var(${varName})`;
804
+ variableMap[tokenPath] = varName;
805
+ variables[varName] = String(rawValue);
806
+ } else tokenMap[tokenPath] = String(rawValue);
807
+ }
808
+ }
809
+ if (theme.modes && isObject(theme.modes) && theme.colors && isObject(theme.colors)) {
810
+ const flatColors = flattenToDotPaths(theme.colors);
811
+ for (const [modeName, modeAliases] of Object.entries(theme.modes)) {
812
+ if (!isObject(modeAliases)) continue;
813
+ const flatAliases = flattenToDotPaths(modeAliases);
814
+ const modeVars = {};
815
+ const modeVals = {};
816
+ for (const [aliasDotKey, colorRef] of Object.entries(flatAliases)) {
817
+ if (typeof colorRef !== "string") continue;
818
+ const varName = `--color-${dotToDash(aliasDotKey)}`;
819
+ const rawValue = flatColors[colorRef];
820
+ modeVals[`colors.${aliasDotKey}`] = rawValue !== void 0 ? String(rawValue) : String(colorRef);
821
+ modeVars[varName] = rawValue !== void 0 ? String(rawValue) : String(colorRef);
822
+ }
823
+ modeVariables[modeName] = modeVars;
824
+ modeTokens[modeName] = modeVals;
825
+ }
826
+ const initialMode = theme.mode;
827
+ if (initialMode && modeVariables[initialMode]) {
828
+ const initialModeVars = {};
829
+ const flatInitialAliases = flattenToDotPaths(theme.modes[initialMode]);
830
+ for (const [aliasDotKey, colorRef] of Object.entries(flatInitialAliases)) {
831
+ if (typeof colorRef !== "string") continue;
832
+ const varName = `--color-${dotToDash(aliasDotKey)}`;
833
+ const paletteVarName = variableMap[`colors.${colorRef}`];
834
+ if (paletteVarName) initialModeVars[varName] = `var(${paletteVarName})`;
835
+ tokenMap[`colors.${aliasDotKey}`] = `var(${varName})`;
836
+ variableMap[`colors.${aliasDotKey}`] = varName;
837
+ }
838
+ Object.assign(variables, initialModeVars);
839
+ }
840
+ }
841
+ return {
842
+ tokenMap,
843
+ variableMap,
844
+ variables,
845
+ modeVariables,
846
+ modeTokens
847
+ };
848
+ }
849
+ /**
850
+ * Resolve token refs ({scale.key}) in all flattened token values.
851
+ * Operates on the flattened tokenMap — does NOT mutate the nested theme.
852
+ */
853
+ function resolveTokenRefs(tokenMap, _variableMap, _emittedScales) {
854
+ for (const [tokenPath, value] of Object.entries(tokenMap)) {
855
+ if (typeof value !== "string") continue;
856
+ if (!value.includes("{")) continue;
857
+ if (value.startsWith("var(")) continue;
858
+ const scaleName = tokenPath.split(".")[0];
859
+ const resolved = value.replace(TOKEN_REF_RE, (match, ref) => {
860
+ if (ref.split(".")[0] === scaleName) {
861
+ console.warn(`[animus] Self-referential token ref {${ref}} in scale '${scaleName}' — skipped`);
862
+ return match;
863
+ }
864
+ let lookupPath = ref;
865
+ let opacity;
866
+ const slashIdx = ref.indexOf("/");
867
+ if (slashIdx !== -1) {
868
+ lookupPath = ref.slice(0, slashIdx);
869
+ opacity = ref.slice(slashIdx + 1);
870
+ }
871
+ const refValue = tokenMap[lookupPath];
872
+ if (refValue === void 0) {
873
+ console.warn(`[animus] Token ref {${ref}} — path '${lookupPath}' not found in token map`);
874
+ return match;
875
+ }
876
+ if (opacity) {
877
+ const alpha = Number.parseInt(opacity, 10);
878
+ if (alpha === 0) return "transparent";
879
+ if (alpha !== 100) return `color-mix(in srgb, ${refValue} ${alpha}%, transparent)`;
880
+ }
881
+ return refValue;
882
+ });
883
+ if (resolved !== value) tokenMap[tokenPath] = resolved;
884
+ }
885
+ }
886
+ /** Build CSS variable blocks from flattened data. */
887
+ function buildVariableCss(rootVariables, breakpointVariables, modeVariables) {
888
+ const parts = [];
889
+ const rootLines = [];
890
+ for (const [varName, value] of Object.entries(rootVariables)) rootLines.push(` ${varName}: ${value};`);
891
+ for (const [varName, value] of Object.entries(breakpointVariables)) rootLines.push(` ${varName}: ${value};`);
892
+ if (rootLines.length > 0) parts.push(`:root {\n${rootLines.join("\n")}\n}`);
893
+ for (const [modeName, modeVars] of Object.entries(modeVariables)) {
894
+ const modeLines = [];
895
+ for (const [varName, value] of Object.entries(modeVars)) modeLines.push(` ${varName}: ${value};`);
896
+ if (modeLines.length > 0) parts.push(`[data-color-mode="${modeName}"] {\n${modeLines.join("\n")}\n}`);
897
+ }
898
+ return parts.join("\n\n");
628
899
  }
629
900
  //#endregion
630
- export { Animus, AnimusExtended, AnimusExtendedWithAll, AnimusWithAll, PropertyBuilder, SystemBuilder, ThemeBuilder, borderShorthand, createComponent, createScale, createSystem, createTheme, createTransform, flattenScale, gridItem, gridItemRatio, numericOrStringScale, numericScale, percentageOrAbsolute, serializeTokens, size, stringScale };
901
+ export { Animus, AnimusExtended, AnimusExtendedWithAll, AnimusWithAll, BUILT_IN_SELECTORS, SystemBuilder, ThemeBuilder, borderShorthand, compose, createClassResolver, createComponent, createComposedFamily, createScale, createSystem, createTheme, createTransform, gridItem, gridItemRatio, numericOrStringScale, numericScale, percentageOrAbsolute, size, stringScale };