@animus-ui/system 0.1.0-next.17 → 0.1.0-next.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,9 @@
1
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
2
+ import { createContext, createElement, forwardRef, useContext, useRef } from "react";
3
+ //#region src/runtime/resolveClasses.ts
4
4
  /**
5
5
  * CSS properties that accept unitless numeric values.
6
6
  * Bare numerics on properties NOT in this set receive `px`.
7
- * Matches @emotion/unitless and React DOM's style handling.
8
7
  */
9
8
  const UNITLESS_PROPERTIES = new Set([
10
9
  "animation-iteration-count",
@@ -52,7 +51,6 @@ const UNITLESS_PROPERTIES = new Set([
52
51
  ]);
53
52
  /**
54
53
  * Apply unit fallback to a value for a given CSS property.
55
- * Unitless numeric values on properties that expect length units receive `px`.
56
54
  */
57
55
  function applyUnitFallback(value, cssProperty) {
58
56
  if (typeof value === "number") {
@@ -63,9 +61,7 @@ function applyUnitFallback(value, cssProperty) {
63
61
  }
64
62
  /**
65
63
  * 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 "|"
64
+ * css_generator's serialize_value_key output format.
69
65
  */
70
66
  function serializeValueKey(value) {
71
67
  if (typeof value === "number" || typeof value === "string") return String(value);
@@ -74,7 +70,6 @@ function serializeValueKey(value) {
74
70
  }
75
71
  /**
76
72
  * Resolve a dynamic prop value through scale lookup → transform → unit fallback.
77
- * Scale lookup uses pre-resolved values shipped from the extraction pipeline.
78
73
  */
79
74
  function resolveValue(value, dc) {
80
75
  const key = String(value);
@@ -86,6 +81,74 @@ function resolveValue(value, dc) {
86
81
  return applyUnitFallback(dc.transform ? dc.transform(value) : value, dc.varName);
87
82
  }
88
83
  /**
84
+ * Resolve className parts from props, using extracted configuration.
85
+ * This is the shared logic between createComponent and createClassResolver.
86
+ */
87
+ function resolveClasses(baseClassName, props, config, systemPropMap, dynamicPropConfig) {
88
+ const classes = [baseClassName];
89
+ let dynStyle;
90
+ if (config.variants) for (const [prop, vc] of Object.entries(config.variants)) {
91
+ const value = props[prop] ?? vc.default;
92
+ if (value != null) classes.push(`${baseClassName}--${prop}-${value}`);
93
+ }
94
+ if (config.compounds) for (const compound of config.compounds) {
95
+ let match = true;
96
+ for (const [prop, expected] of Object.entries(compound.conditions)) {
97
+ const current = props[prop] ?? config.variants?.[prop]?.default;
98
+ if (Array.isArray(expected) ? !expected.includes(current) : current !== expected) {
99
+ match = false;
100
+ break;
101
+ }
102
+ }
103
+ if (match) classes.push(compound.className);
104
+ }
105
+ if (config.states) {
106
+ for (const state of config.states) if (props[state]) classes.push(`${baseClassName}--${state}`);
107
+ }
108
+ const systemPropNames = config.systemPropNames || [];
109
+ if (systemPropNames.length > 0) {
110
+ const { customPropMap, customDynamicConfig } = config;
111
+ for (const propName of systemPropNames) {
112
+ if (!(propName in props)) continue;
113
+ const propValue = props[propName];
114
+ if (propValue == null) continue;
115
+ const key = serializeValueKey(propValue);
116
+ const cls = customPropMap?.[propName]?.[key] ?? systemPropMap?.[propName]?.[key];
117
+ if (cls) classes.push(cls);
118
+ else {
119
+ const dc = customDynamicConfig?.[propName] ?? dynamicPropConfig?.[propName];
120
+ if (dc) {
121
+ if (!dynStyle) dynStyle = {};
122
+ if (typeof propValue === "object" && propValue !== null && !Array.isArray(propValue)) for (const [bp, bpVal] of Object.entries(propValue)) {
123
+ if (bpVal == null) continue;
124
+ if (bp === "_") {
125
+ classes.push(dc.slotClass);
126
+ const finalVal = resolveValue(bpVal, dc);
127
+ dynStyle[dc.varName] = finalVal;
128
+ } else {
129
+ classes.push(`${dc.slotClass}-${bp}`);
130
+ const varName = `${dc.varName}-${bp}`;
131
+ const finalVal = resolveValue(bpVal, dc);
132
+ dynStyle[varName] = finalVal;
133
+ }
134
+ }
135
+ else {
136
+ classes.push(dc.slotClass);
137
+ const finalVal = resolveValue(propValue, dc);
138
+ dynStyle[dc.varName] = finalVal;
139
+ }
140
+ }
141
+ }
142
+ }
143
+ }
144
+ return {
145
+ classes,
146
+ dynamicStyle: dynStyle
147
+ };
148
+ }
149
+ //#endregion
150
+ //#region src/runtime/index.ts
151
+ /**
89
152
  * Create a lightweight component that applies extracted CSS class names.
90
153
  * Replaces Emotion's styled() for extracted components.
91
154
  *
@@ -105,74 +168,17 @@ function createComponent(element, className, config, systemPropMap, dynamicPropC
105
168
  const stateProps = config.states || [];
106
169
  const systemPropNames = config.systemPropNames || [];
107
170
  const filterProps = new Set([
171
+ "as",
108
172
  ...variantProps,
109
173
  ...stateProps,
110
174
  ...systemPropNames
111
175
  ]);
112
- const isComponentElement = typeof element !== "string";
113
176
  const Component = forwardRef((props, ref) => {
114
- const classes = [className];
177
+ const renderElement = props.as || element;
178
+ const isComponentElement = typeof renderElement !== "string";
115
179
  const prevDynKey = useRef("");
116
180
  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.compounds) for (const compound of config.compounds) {
122
- let match = true;
123
- for (const [prop, expected] of Object.entries(compound.conditions)) {
124
- const current = props[prop] ?? config.variants?.[prop]?.default;
125
- if (Array.isArray(expected) ? !expected.includes(current) : current !== expected) {
126
- match = false;
127
- break;
128
- }
129
- }
130
- if (match) classes.push(compound.className);
131
- }
132
- if (config.states) {
133
- for (const state of config.states) if (props[state]) classes.push(`${className}--${state}`);
134
- }
135
- let dynKeyParts;
136
- let dynStyle;
137
- if (systemPropNames.length > 0) {
138
- const { customPropMap, customDynamicConfig } = config;
139
- for (const propName of systemPropNames) {
140
- if (!(propName in props)) continue;
141
- const propValue = props[propName];
142
- if (propValue == null) continue;
143
- const key = serializeValueKey(propValue);
144
- const cls = customPropMap?.[propName]?.[key] ?? systemPropMap?.[propName]?.[key];
145
- if (cls) classes.push(cls);
146
- else {
147
- const dc = customDynamicConfig?.[propName] ?? dynamicPropConfig?.[propName];
148
- if (dc) {
149
- if (!dynKeyParts) dynKeyParts = [];
150
- if (!dynStyle) dynStyle = {};
151
- if (typeof propValue === "object" && propValue !== null && !Array.isArray(propValue)) for (const [bp, bpVal] of Object.entries(propValue)) {
152
- if (bpVal == null) continue;
153
- if (bp === "_") {
154
- classes.push(dc.slotClass);
155
- const finalVal = resolveValue(bpVal, dc);
156
- dynStyle[dc.varName] = finalVal;
157
- dynKeyParts.push(`${dc.varName}:${finalVal}`);
158
- } else {
159
- classes.push(`${dc.slotClass}-${bp}`);
160
- const varName = `${dc.varName}-${bp}`;
161
- const finalVal = resolveValue(bpVal, dc);
162
- dynStyle[varName] = finalVal;
163
- dynKeyParts.push(`${varName}:${finalVal}`);
164
- }
165
- }
166
- else {
167
- classes.push(dc.slotClass);
168
- const finalVal = resolveValue(propValue, dc);
169
- dynStyle[dc.varName] = finalVal;
170
- dynKeyParts.push(`${dc.varName}:${finalVal}`);
171
- }
172
- }
173
- }
174
- }
175
- }
181
+ const { classes, dynamicStyle } = resolveClasses(className, props, config, systemPropMap, dynamicPropConfig);
176
182
  if (props.className) classes.push(props.className);
177
183
  const domProps = {
178
184
  ref,
@@ -184,25 +190,41 @@ function createComponent(element, className, config, systemPropMap, dynamicPropC
184
190
  if (!isComponentElement) {}
185
191
  domProps[key] = value;
186
192
  }
187
- if (dynKeyParts && dynStyle) {
188
- const dynKey = dynKeyParts.join("|");
193
+ if (dynamicStyle) {
194
+ const dynKey = Object.entries(dynamicStyle).map(([k, v]) => `${k}:${v}`).join("|");
189
195
  if (dynKey !== prevDynKey.current) {
190
196
  prevDynKey.current = dynKey;
191
- prevDynStyle.current = dynStyle;
197
+ prevDynStyle.current = dynamicStyle;
192
198
  }
193
199
  domProps.style = props.style ? {
194
200
  ...props.style,
195
201
  ...prevDynStyle.current
196
202
  } : prevDynStyle.current;
197
203
  }
198
- return createElement(element, domProps);
204
+ return createElement(renderElement, domProps);
199
205
  });
200
206
  Component.displayName = className;
207
+ Component.__variantKeys = new Set(variantProps);
201
208
  return Object.assign(Component, { extend: () => {
202
209
  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.`);
203
210
  } });
204
211
  }
205
212
  //#endregion
213
+ //#region src/runtime/createClassResolver.ts
214
+ /**
215
+ * createClassResolver — framework-agnostic className resolution.
216
+ *
217
+ * Produced by .asClass() terminal. Same resolution logic as createComponent
218
+ * (variants, states, compounds, system props) but returns a className string
219
+ * instead of a React element.
220
+ */
221
+ function createClassResolver(className, config, systemPropMap, dynamicPropConfig) {
222
+ return (props) => {
223
+ const { classes } = resolveClasses(className, props || {}, config, systemPropMap, dynamicPropConfig);
224
+ return classes.join(" ");
225
+ };
226
+ }
227
+ //#endregion
206
228
  //#region src/AnimusExtended.ts
207
229
  function deepMerge$1(target, source) {
208
230
  const result = { ...target };
@@ -242,6 +264,9 @@ var AnimusExtendedWithAll = class {
242
264
  const extendFn = this.extend.bind(this);
243
265
  return Object.assign(Component, { extend: extendFn });
244
266
  }
267
+ asClass() {
268
+ return createClassResolver("", this._buildComponentConfig());
269
+ }
245
270
  build() {
246
271
  return Object.assign((() => ({})), { extend: this.extend.bind(this) });
247
272
  }
@@ -350,6 +375,9 @@ var AnimusWithAll = class {
350
375
  const extendFn = this.extend.bind(this);
351
376
  return Object.assign(Component, { extend: extendFn });
352
377
  }
378
+ asClass() {
379
+ return createClassResolver("", this._buildComponentConfig());
380
+ }
353
381
  build() {
354
382
  return Object.assign((() => ({})), { extend: this.extend.bind(this) });
355
383
  }
@@ -434,6 +462,67 @@ var Animus = class extends AnimusWithBase {
434
462
  }
435
463
  };
436
464
  //#endregion
465
+ //#region src/compose.ts
466
+ const EMPTY_SHARED = {};
467
+ /**
468
+ * Compose independently-authored Animus components into a sealed,
469
+ * namespaced component family with shared variant propagation via
470
+ * React context.
471
+ *
472
+ * - **Enforce**: TypeScript ensures shared keys exist on Root (the
473
+ * provider). Non-Root slots that have the key consume it from
474
+ * context; slots without the key are unaffected.
475
+ * - **Wire**: Root provides shared variant values via context.
476
+ * Child slots consume from context. Direct props override context.
477
+ * - **Seal**: Output components are plain ForwardRefExoticComponent —
478
+ * no `.extend()`, no builder methods. One-way door from builder-land
479
+ * to component-land.
480
+ */
481
+ function compose(slots, options) {
482
+ const sharedKeys = Object.keys(options.shared);
483
+ const FamilyContext = createContext(EMPTY_SHARED);
484
+ const rootSlot = slots.Root ?? slots.root;
485
+ const familyName = rootSlot?.displayName ? rootSlot.displayName.replace(/[-_].*$/, "") : "Composed";
486
+ const result = {};
487
+ for (const [name, SourceComponent] of Object.entries(slots)) {
488
+ const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);
489
+ if (name.toLowerCase() === "root") {
490
+ const RootWrapper = forwardRef((props, ref) => {
491
+ const sharedValues = {};
492
+ for (const key of sharedKeys) if (key in props) sharedValues[key] = props[key];
493
+ return createElement(FamilyContext.Provider, { value: sharedKeys.length > 0 ? sharedValues : EMPTY_SHARED }, createElement(SourceComponent, {
494
+ ...props,
495
+ ref
496
+ }));
497
+ });
498
+ RootWrapper.displayName = `${familyName}.${capitalizedName}`;
499
+ result[capitalizedName] = RootWrapper;
500
+ } else {
501
+ const knownKeys = SourceComponent.__variantKeys;
502
+ const ChildWrapper = forwardRef((props, ref) => {
503
+ const shared = useContext(FamilyContext);
504
+ let merged;
505
+ if (knownKeys && sharedKeys.length > 0) {
506
+ const filtered = {};
507
+ for (const key of sharedKeys) if (knownKeys.has(key) && key in shared) filtered[key] = shared[key];
508
+ merged = {
509
+ ...filtered,
510
+ ...props,
511
+ ref
512
+ };
513
+ } else merged = {
514
+ ...props,
515
+ ref
516
+ };
517
+ return createElement(SourceComponent, merged);
518
+ });
519
+ ChildWrapper.displayName = `${familyName}.${capitalizedName}`;
520
+ result[capitalizedName] = ChildWrapper;
521
+ }
522
+ }
523
+ return result;
524
+ }
525
+ //#endregion
437
526
  //#region src/PropertyBuilder.ts
438
527
  var PropertyBuilder = class PropertyBuilder {
439
528
  #props;
@@ -770,46 +859,39 @@ function validateColors(colors) {
770
859
  for (const [key, value] of Object.entries(colors)) if (isObject(value)) validateColors(value);
771
860
  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.`);
772
861
  }
773
- var ThemeBuilder = class {
862
+ var ThemeBuilder = class ThemeBuilder {
774
863
  #theme = {};
864
+ #emittedScales = /* @__PURE__ */ new Set();
775
865
  constructor(baseTheme) {
776
866
  this.#theme = baseTheme;
777
867
  }
778
- /**
779
- *
780
- * @param key A key of the current theme to transform into CSS Variables and Variable References
781
- * @example .createScaleVariables('fontSize')
782
- */
783
- createScaleVariables(key) {
784
- const { variables, tokens } = serializeTokens(this.#theme[key], key, this.#theme);
785
- this.#theme = merge({}, this.#theme, {
786
- [key]: tokens,
787
- _variables: { [key]: variables },
788
- _tokens: { [key]: this.#theme[key] }
789
- });
790
- return this;
868
+ /** Create a new builder checkpoint, carrying forward emittedScales state. */
869
+ #checkpoint(nextTheme) {
870
+ const next = new ThemeBuilder(nextTheme);
871
+ for (const s of this.#emittedScales) next.#emittedScales.add(s);
872
+ return next;
791
873
  }
792
874
  /**
793
- *
794
- * @param colors A map of color tokens to add to the theme. These tokens are immediately converted to CSS Variables `--color-${key}`.
875
+ * @param colors A map of color tokens. Immediately converted to CSS variables `--color-${key}`.
795
876
  * @example .addColors({ navy: 'navy', hyper: 'purple' })
796
877
  */
797
878
  addColors(colors) {
798
879
  validateColors(colors);
799
880
  const flatColors = flattenScale(colors);
800
881
  const { variables, tokens } = serializeTokens(flatColors, "color", this.#theme);
801
- this.#theme = merge({}, this.#theme, {
882
+ const nextTheme = merge({}, this.#theme, {
802
883
  colors: tokens,
803
884
  _variables: { root: variables },
804
885
  _tokens: { colors: flatColors }
805
886
  });
806
- return this;
887
+ const next = this.#checkpoint(nextTheme);
888
+ next.#emittedScales.add("colors");
889
+ return next;
807
890
  }
808
891
  /**
809
- *
810
- * @param initialMode A key of the object passed for modes. This sets the default state for the theme and transforms the correct variables.
811
- * @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`
812
- * @example .addColorModes('light', { light: { primary: 'hyper' }, { dark: { primary: 'navy' } } })
892
+ * @param initialMode Default color mode key.
893
+ * @param modeConfig Map of color modes with semantic aliases pointing to palette keys.
894
+ * @example .addColorModes('dark', { dark: { primary: 'ember' }, light: { primary: 'void' } })
813
895
  */
814
896
  addColorModes(initialMode, modeConfig) {
815
897
  const availableColors = this.#theme._tokens?.colors ? Object.keys(this.#theme._tokens.colors) : Object.keys(this.#theme.colors || {});
@@ -818,7 +900,7 @@ var ThemeBuilder = class {
818
900
  const modes = mapValues(modeConfig, (mode) => flattenScale(mode));
819
901
  const { tokens: colors, variables } = serializeTokens(mapValues(merge({}, this.#theme.modes?.[initialMode], modes[initialMode]), (color) => this.#theme.colors[color]), "color", this.#theme);
820
902
  const getColorValue = (color) => this.#theme._tokens?.colors?.[color];
821
- this.#theme = merge({}, this.#theme, {
903
+ const nextTheme = merge({}, this.#theme, {
822
904
  colors,
823
905
  modes,
824
906
  mode: initialMode,
@@ -826,36 +908,46 @@ var ThemeBuilder = class {
826
908
  _variables: { mode: variables },
827
909
  _tokens: { modes: mapValues(modes, (mode) => mapValues(mode, getColorValue)) }
828
910
  });
829
- return this;
911
+ return this.#checkpoint(nextTheme);
830
912
  }
831
913
  /**
914
+ * Add a named scale to the theme.
915
+ *
916
+ * @param config.name - Scale name (e.g. 'space', 'sizes')
917
+ * @param config.values - Scale value map
918
+ * @param config.emit - When true, generates CSS variables (default: false)
832
919
  *
833
- * @param key A new key of theme
834
- * @param createScale A function that accepts the current theme and returns a new object of scale values.
835
- * @example .addScale('fonts', () => ({ basic: 'Gotham', cool: 'Wingdings' }))
920
+ * @example
921
+ * .addScale({ name: 'space', values: { 0: '0', 8: '0.5rem', 16: '1rem' } })
922
+ * .addScale({ name: 'sizes', emit: true, values: { navHeight: '48px' } })
836
923
  */
837
- addScale(key, createScale) {
838
- this.#theme = merge({}, this.#theme, { [key]: flattenScale(createScale(this.#theme)) });
839
- return this;
924
+ addScale(config) {
925
+ const { name, values, emit } = config;
926
+ const flattened = flattenScale(values);
927
+ let nextTheme;
928
+ if (emit) {
929
+ const { variables, tokens } = serializeTokens(flattened, name, this.#theme);
930
+ nextTheme = merge({}, this.#theme, {
931
+ [name]: tokens,
932
+ _variables: { [name]: variables },
933
+ _tokens: { [name]: flattened }
934
+ });
935
+ } else nextTheme = merge({}, this.#theme, { [name]: flattened });
936
+ const next = this.#checkpoint(nextTheme);
937
+ if (emit) next.#emittedScales.add(name);
938
+ return next;
840
939
  }
841
940
  /**
842
- *
843
- * @param key A current key of theme to be updated with new or computed values
844
- * @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.
845
- * @example .updateScale('fonts', ({ basic }) => ({ basicFallback: `{basic}, Montserrat` }))
941
+ * @param key A current key of theme to update with computed values.
942
+ * @example .updateScale('fonts', ({ basic }) => ({ basicFallback: `${basic}, Montserrat` }))
846
943
  */
847
944
  updateScale(key, updateFn) {
848
- this.#theme = merge({}, this.#theme, { [key]: updateFn(this.#theme[key]) });
849
- return this;
945
+ const nextTheme = merge({}, this.#theme, { [key]: updateFn(this.#theme[key]) });
946
+ return this.#checkpoint(nextTheme);
850
947
  }
851
- /**
852
- * This finalizes the theme build and returns the final theme and variables to be provided.
853
- * Simplify flattens the deeply nested MergeTheme chain into a shallow object type.
854
- *
855
- * The returned theme object also has a non-enumerable `.manifest` property containing
856
- * a structured ThemeManifest for plugin consumption.
857
- */
948
+ /** Finalize the theme build. Returns the theme with a non-enumerable `.manifest` property. */
858
949
  build() {
950
+ resolveThemeTokenRefs(this.#theme, this.#emittedScales);
859
951
  const { variables } = serializeTokens(mapValues(this.#theme.breakpoints, (val) => `${val}px`), "breakpoint", this.#theme);
860
952
  const theme = merge({}, this.#theme, {
861
953
  _variables: { breakpoints: variables },
@@ -874,6 +966,54 @@ var ThemeBuilder = class {
874
966
  function createTheme(base) {
875
967
  return new ThemeBuilder(base);
876
968
  }
969
+ /** Token ref pattern: {scale.key} */
970
+ const TOKEN_REF_RE = /\{([^}]+)\}/g;
971
+ /**
972
+ * Resolve token refs ({scale.key}) in all scale values.
973
+ * Only refs to emitted scales (those with CSS variables) are valid.
974
+ * Runs once at build() time after all scales have been collected.
975
+ */
976
+ function resolveThemeTokenRefs(theme, emittedScales) {
977
+ for (const [scaleName, scaleValue] of Object.entries(theme)) {
978
+ if (scaleName.startsWith("_")) continue;
979
+ if (scaleName === "breakpoints" || scaleName === "mode" || scaleName === "modes") continue;
980
+ if (typeof scaleValue === "function") continue;
981
+ if (!isObject(scaleValue)) continue;
982
+ for (const [key, value] of Object.entries(scaleValue)) {
983
+ if (typeof value !== "string") continue;
984
+ if (!value.includes("{")) continue;
985
+ const resolved = value.replace(TOKEN_REF_RE, (match, ref) => {
986
+ const dotIdx = ref.indexOf(".");
987
+ if (dotIdx === -1) return match;
988
+ const refScale = ref.slice(0, dotIdx);
989
+ const refKey = ref.slice(dotIdx + 1);
990
+ if (refScale === scaleName) {
991
+ console.warn(`[animus] Self-referential token ref {${ref}} in scale '${scaleName}' — skipped`);
992
+ return match;
993
+ }
994
+ const targetScale = theme[refScale];
995
+ if (!targetScale || !isObject(targetScale)) {
996
+ console.warn(`[animus] Token ref {${ref}} references unknown scale '${refScale}'`);
997
+ return match;
998
+ }
999
+ const resolvedValue = targetScale[refKey];
1000
+ if (resolvedValue === void 0) {
1001
+ console.warn(`[animus] Token ref {${ref}} — key '${refKey}' not found in scale '${refScale}'`);
1002
+ return match;
1003
+ }
1004
+ return String(resolvedValue);
1005
+ });
1006
+ if (resolved !== value) {
1007
+ scaleValue[key] = resolved;
1008
+ if (theme._tokens?.[scaleName]) theme._tokens[scaleName][key] = resolved;
1009
+ if (theme._variables?.[scaleName]) {
1010
+ const varName = `--${scaleName}-${key.replace("$", "")}`;
1011
+ if (theme._variables[scaleName][varName] !== void 0) theme._variables[scaleName][varName] = resolved;
1012
+ }
1013
+ }
1014
+ }
1015
+ }
1016
+ }
877
1017
  /**
878
1018
  * Assemble a ThemeManifest from the built theme object.
879
1019
  *
@@ -952,4 +1092,4 @@ function flattenModeTokensCss(lines, obj, prefix) {
952
1092
  }
953
1093
  }
954
1094
  //#endregion
955
- export { Animus, AnimusExtended, AnimusExtendedWithAll, AnimusWithAll, PropertyBuilder, SystemBuilder, ThemeBuilder, borderShorthand, createComponent, createScale, createSystem, createTheme, createTransform, flattenScale, gridItem, gridItemRatio, numericOrStringScale, numericScale, percentageOrAbsolute, serializeTokens, size, stringScale };
1095
+ export { Animus, AnimusExtended, AnimusExtendedWithAll, AnimusWithAll, PropertyBuilder, SystemBuilder, ThemeBuilder, borderShorthand, compose, createClassResolver, createComponent, createScale, createSystem, createTheme, createTransform, flattenScale, gridItem, gridItemRatio, numericOrStringScale, numericScale, percentageOrAbsolute, serializeTokens, size, stringScale };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * createClassResolver — framework-agnostic className resolution.
3
+ *
4
+ * Produced by .asClass() terminal. Same resolution logic as createComponent
5
+ * (variants, states, compounds, system props) but returns a className string
6
+ * instead of a React element.
7
+ */
8
+ import { type ClassResolverConfig, type DynamicPropConfig, type SystemPropMap } from './resolveClasses';
9
+ export declare function createClassResolver(className: string, config: ClassResolverConfig, systemPropMap?: SystemPropMap, dynamicPropConfig?: DynamicPropConfig): (props?: Record<string, unknown>) => string;
10
+ //# sourceMappingURL=createClassResolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createClassResolver.d.ts","sourceRoot":"","sources":["../../src/runtime/createClassResolver.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EAEtB,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAC;AAE1B,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,mBAAmB,EAC3B,aAAa,CAAC,EAAE,aAAa,EAC7B,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAW7C"}
@@ -1,28 +1,7 @@
1
1
  import { forwardRef } from 'react';
2
- interface VariantConfig {
3
- options: string[];
4
- default?: string;
2
+ import { type ClassResolverConfig, type DynamicPropConfig, type SystemPropMap } from './resolveClasses';
3
+ interface ComponentConfig extends ClassResolverConfig {
5
4
  }
6
- interface CompoundConfig {
7
- conditions: Record<string, string | string[]>;
8
- className: string;
9
- }
10
- interface ComponentConfig {
11
- variants?: Record<string, VariantConfig>;
12
- compounds?: CompoundConfig[];
13
- states?: string[];
14
- systemPropNames?: string[];
15
- customPropMap?: Record<string, Record<string, string>>;
16
- customDynamicConfig?: DynamicPropConfig;
17
- }
18
- type SystemPropMap = Record<string, Record<string, string>>;
19
- type DynamicPropConfig = Record<string, {
20
- varName: string;
21
- slotClass: string;
22
- transformName?: string;
23
- transform?: (value: string | number) => string | number;
24
- scaleValues?: Record<string, string>;
25
- }>;
26
5
  type ElementType = string | React.ComponentType<any>;
27
6
  type AnimusComponent = ReturnType<typeof forwardRef> & {
28
7
  extend: () => never;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,UAAU,EAAU,MAAM,OAAO,CAAC;AAE1D,UAAU,aAAa;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,cAAc;IACtB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC9C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,eAAe;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACzC,SAAS,CAAC,EAAE,cAAc,EAAE,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACvD,mBAAmB,CAAC,EAAE,iBAAiB,CAAC;CACzC;AAED,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAE5D,KAAK,iBAAiB,GAAG,MAAM,CAC7B,MAAM,EACN;IACE,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,GAAG,MAAM,CAAC;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC,CACF,CAAC;AAIF,KAAK,WAAW,GAAG,MAAM,GAAG,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;AAErD,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG;IACrD,MAAM,EAAE,MAAM,KAAK,CAAC;CACrB,CAAC;AAwHF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,eAAe,EACvB,aAAa,CAAC,EAAE,aAAa,EAC7B,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,eAAe,CAuLjB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,UAAU,EAAU,MAAM,OAAO,CAAC;AAE1D,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EAEtB,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAC;AAE1B,UAAU,eAAgB,SAAQ,mBAAmB;CAAG;AAIxD,KAAK,WAAW,GAAG,MAAM,GAAG,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;AAErD,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG;IACrD,MAAM,EAAE,MAAM,KAAK,CAAC;CACrB,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,eAAe,EACvB,aAAa,CAAC,EAAE,aAAa,EAC7B,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,eAAe,CAoFjB"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Shared className resolution logic used by both createComponent (React)
3
+ * and createClassResolver (framework-agnostic).
4
+ *
5
+ * Factored to ensure behavioral parity between .asElement() and .asClass() outputs.
6
+ */
7
+ interface VariantConfig {
8
+ options: string[];
9
+ default?: string;
10
+ }
11
+ interface CompoundConfig {
12
+ conditions: Record<string, string | string[]>;
13
+ className: string;
14
+ }
15
+ export interface ClassResolverConfig {
16
+ variants?: Record<string, VariantConfig>;
17
+ compounds?: CompoundConfig[];
18
+ states?: string[];
19
+ systemPropNames?: string[];
20
+ customPropMap?: Record<string, Record<string, string>>;
21
+ customDynamicConfig?: DynamicPropConfig;
22
+ }
23
+ export type SystemPropMap = Record<string, Record<string, string>>;
24
+ export type DynamicPropConfig = Record<string, {
25
+ varName: string;
26
+ slotClass: string;
27
+ transformName?: string;
28
+ transform?: (value: string | number) => string | number;
29
+ scaleValues?: Record<string, string>;
30
+ }>;
31
+ /**
32
+ * Apply unit fallback to a value for a given CSS property.
33
+ */
34
+ export declare function applyUnitFallback(value: string | number, cssProperty: string): string;
35
+ /**
36
+ * Serialize a system prop value to a lookup key matching the Rust
37
+ * css_generator's serialize_value_key output format.
38
+ */
39
+ export declare function serializeValueKey(value: unknown): string;
40
+ /**
41
+ * Resolve a dynamic prop value through scale lookup → transform → unit fallback.
42
+ */
43
+ export declare function resolveValue(value: unknown, dc: {
44
+ varName: string;
45
+ transform?: (value: string | number) => string | number;
46
+ scaleValues?: Record<string, string>;
47
+ }): string;
48
+ export interface ClassResolution {
49
+ classes: string[];
50
+ dynamicStyle?: Record<string, string>;
51
+ }
52
+ /**
53
+ * Resolve className parts from props, using extracted configuration.
54
+ * This is the shared logic between createComponent and createClassResolver.
55
+ */
56
+ export declare function resolveClasses(baseClassName: string, props: Record<string, any>, config: ClassResolverConfig, systemPropMap?: SystemPropMap, dynamicPropConfig?: DynamicPropConfig): ClassResolution;
57
+ export {};
58
+ //# sourceMappingURL=resolveClasses.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveClasses.d.ts","sourceRoot":"","sources":["../../src/runtime/resolveClasses.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,UAAU,aAAa;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,cAAc;IACtB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC9C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACzC,SAAS,CAAC,EAAE,cAAc,EAAE,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACvD,mBAAmB,CAAC,EAAE,iBAAiB,CAAC;CACzC;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAEnE,MAAM,MAAM,iBAAiB,GAAG,MAAM,CACpC,MAAM,EACN;IACE,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,GAAG,MAAM,CAAC;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC,CACF,CAAC;AAmDF;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,GAAG,MAAM,EACtB,WAAW,EAAE,MAAM,GAClB,MAAM,CAQR;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAWxD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,OAAO,EACd,EAAE,EAAE;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,GAAG,MAAM,CAAC;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC,GACA,MAAM,CAaR;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC1B,MAAM,EAAE,mBAAmB,EAC3B,aAAa,CAAC,EAAE,aAAa,EAC7B,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,eAAe,CAgGjB"}