@animus-ui/system 0.1.0-next.23 → 0.1.0-next.31
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/Animus.d.ts +2 -7
- package/dist/Animus.d.ts.map +1 -1
- package/dist/AnimusExtended.d.ts +2 -7
- package/dist/AnimusExtended.d.ts.map +1 -1
- package/dist/SystemBuilder.d.ts +16 -13
- package/dist/SystemBuilder.d.ts.map +1 -1
- package/dist/compose.d.ts +2 -4
- package/dist/compose.d.ts.map +1 -1
- package/dist/createClassResolver-Dny76K15.js +227 -0
- package/dist/groups/index.js +1 -1
- package/dist/index.d.ts +4 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +330 -552
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime-entry.d.ts +9 -0
- package/dist/runtime-entry.d.ts.map +1 -0
- package/dist/runtime-entry.js +2 -0
- package/dist/theme/createTheme.d.ts +44 -66
- package/dist/theme/createTheme.d.ts.map +1 -1
- package/dist/theme/index.d.ts +0 -2
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/utils.d.ts +20 -0
- package/dist/theme/utils.d.ts.map +1 -1
- package/dist/types/component.d.ts +45 -26
- package/dist/types/component.d.ts.map +1 -1
- package/dist/types/theme.d.ts +31 -11
- package/dist/types/theme.d.ts.map +1 -1
- package/dist/utils/deepMerge.d.ts +5 -0
- package/dist/utils/deepMerge.d.ts.map +1 -0
- package/package.json +6 -1
- package/dist/PropertyBuilder.d.ts +0 -11
- package/dist/PropertyBuilder.d.ts.map +0 -1
- /package/dist/{size-Dge_rsuz.js → size-BjymBo7z.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,237 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { n as createComponent, t as createClassResolver } from "./createClassResolver-Dny76K15.js";
|
|
2
|
+
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-BjymBo7z.js";
|
|
3
|
+
import { createContext, createElement, forwardRef, useContext } from "react";
|
|
4
|
+
//#region src/utils/deepMerge.ts
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
-
* Bare numerics on properties NOT in this set receive `px`.
|
|
7
|
-
*/
|
|
8
|
-
const UNITLESS_PROPERTIES = new Set([
|
|
9
|
-
"animation-iteration-count",
|
|
10
|
-
"border-image-outset",
|
|
11
|
-
"border-image-slice",
|
|
12
|
-
"border-image-width",
|
|
13
|
-
"box-flex",
|
|
14
|
-
"box-flex-group",
|
|
15
|
-
"box-ordinal-group",
|
|
16
|
-
"column-count",
|
|
17
|
-
"columns",
|
|
18
|
-
"flex",
|
|
19
|
-
"flex-grow",
|
|
20
|
-
"flex-positive",
|
|
21
|
-
"flex-shrink",
|
|
22
|
-
"flex-negative",
|
|
23
|
-
"flex-order",
|
|
24
|
-
"font-weight",
|
|
25
|
-
"grid-area",
|
|
26
|
-
"grid-column",
|
|
27
|
-
"grid-column-end",
|
|
28
|
-
"grid-column-span",
|
|
29
|
-
"grid-column-start",
|
|
30
|
-
"grid-row",
|
|
31
|
-
"grid-row-end",
|
|
32
|
-
"grid-row-span",
|
|
33
|
-
"grid-row-start",
|
|
34
|
-
"line-clamp",
|
|
35
|
-
"line-height",
|
|
36
|
-
"opacity",
|
|
37
|
-
"order",
|
|
38
|
-
"orphans",
|
|
39
|
-
"tab-size",
|
|
40
|
-
"widows",
|
|
41
|
-
"z-index",
|
|
42
|
-
"zoom",
|
|
43
|
-
"fill-opacity",
|
|
44
|
-
"flood-opacity",
|
|
45
|
-
"stop-opacity",
|
|
46
|
-
"stroke-dasharray",
|
|
47
|
-
"stroke-dashoffset",
|
|
48
|
-
"stroke-miterlimit",
|
|
49
|
-
"stroke-opacity",
|
|
50
|
-
"stroke-width"
|
|
51
|
-
]);
|
|
52
|
-
/**
|
|
53
|
-
* Apply unit fallback to a value for a given CSS property.
|
|
54
|
-
*/
|
|
55
|
-
function applyUnitFallback(value, cssProperty) {
|
|
56
|
-
if (typeof value === "number") {
|
|
57
|
-
if (UNITLESS_PROPERTIES.has(cssProperty)) return String(value);
|
|
58
|
-
return `${value}px`;
|
|
59
|
-
}
|
|
60
|
-
return String(value);
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Serialize a system prop value to a lookup key matching the Rust
|
|
64
|
-
* css_generator's serialize_value_key output format.
|
|
65
|
-
*/
|
|
66
|
-
function serializeValueKey(value) {
|
|
67
|
-
if (typeof value === "number" || typeof value === "string") return String(value);
|
|
68
|
-
if (typeof value === "object" && value !== null && !Array.isArray(value)) return Object.keys(value).sort().map((k) => `${k}:${value[k]}`).join("|");
|
|
69
|
-
return String(value);
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Resolve a dynamic prop value through scale lookup → transform → unit fallback.
|
|
73
|
-
*/
|
|
74
|
-
function resolveValue(value, dc) {
|
|
75
|
-
const key = String(value);
|
|
76
|
-
const scaleResolved = dc.scaleValues?.[key];
|
|
77
|
-
if (scaleResolved != null) {
|
|
78
|
-
const transformed = dc.transform ? dc.transform(scaleResolved) : scaleResolved;
|
|
79
|
-
return String(transformed);
|
|
80
|
-
}
|
|
81
|
-
return applyUnitFallback(dc.transform ? dc.transform(value) : value, dc.varName);
|
|
82
|
-
}
|
|
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
|
-
/**
|
|
152
|
-
* Create a lightweight component that applies extracted CSS class names.
|
|
153
|
-
* Replaces Emotion's styled() for extracted components.
|
|
154
|
-
*
|
|
155
|
-
* The element parameter accepts either an HTML tag string (e.g. 'button') or
|
|
156
|
-
* a React component reference (e.g. NextLink). When a component reference is
|
|
157
|
-
* used, prop forwarding skips the HTML-attribute validity check — all
|
|
158
|
-
* non-filtered props are forwarded to the component.
|
|
159
|
-
*
|
|
160
|
-
* The optional systemPropMap parameter provides the shared prop→value→className
|
|
161
|
-
* lookup table, served as a virtual module by the Vite plugin.
|
|
162
|
-
*
|
|
163
|
-
* The optional dynamicPropConfig parameter provides CSS variable fallback
|
|
164
|
-
* metadata for props with detected dynamic usage.
|
|
165
|
-
*/
|
|
166
|
-
function createComponent(element, className, config, systemPropMap, dynamicPropConfig) {
|
|
167
|
-
const variantProps = config.variants ? Object.keys(config.variants) : [];
|
|
168
|
-
const stateProps = config.states || [];
|
|
169
|
-
const systemPropNames = config.systemPropNames || [];
|
|
170
|
-
const filterProps = new Set([
|
|
171
|
-
"as",
|
|
172
|
-
...variantProps,
|
|
173
|
-
...stateProps,
|
|
174
|
-
...systemPropNames
|
|
175
|
-
]);
|
|
176
|
-
const Component = forwardRef((props, ref) => {
|
|
177
|
-
const renderElement = props.as || element;
|
|
178
|
-
const isComponentElement = typeof renderElement !== "string";
|
|
179
|
-
const prevDynKey = useRef("");
|
|
180
|
-
const prevDynStyle = useRef(null);
|
|
181
|
-
const { classes, dynamicStyle } = resolveClasses(className, props, config, systemPropMap, dynamicPropConfig);
|
|
182
|
-
if (props.className) classes.push(props.className);
|
|
183
|
-
const domProps = {
|
|
184
|
-
ref,
|
|
185
|
-
className: classes.join(" ")
|
|
186
|
-
};
|
|
187
|
-
for (const [key, value] of Object.entries(props)) {
|
|
188
|
-
if (key === "className") continue;
|
|
189
|
-
if (filterProps.has(key)) continue;
|
|
190
|
-
if (!isComponentElement) {}
|
|
191
|
-
domProps[key] = value;
|
|
192
|
-
}
|
|
193
|
-
if (dynamicStyle) {
|
|
194
|
-
const dynKey = Object.entries(dynamicStyle).map(([k, v]) => `${k}:${v}`).join("|");
|
|
195
|
-
if (dynKey !== prevDynKey.current) {
|
|
196
|
-
prevDynKey.current = dynKey;
|
|
197
|
-
prevDynStyle.current = dynamicStyle;
|
|
198
|
-
}
|
|
199
|
-
domProps.style = props.style ? {
|
|
200
|
-
...props.style,
|
|
201
|
-
...prevDynStyle.current
|
|
202
|
-
} : prevDynStyle.current;
|
|
203
|
-
}
|
|
204
|
-
return createElement(renderElement, domProps);
|
|
205
|
-
});
|
|
206
|
-
Component.displayName = className;
|
|
207
|
-
Component.__variantKeys = new Set(variantProps);
|
|
208
|
-
return Object.assign(Component, { extend: () => {
|
|
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.`);
|
|
210
|
-
} });
|
|
211
|
-
}
|
|
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.
|
|
6
|
+
* Deep merge utility — replaces lodash.merge for variant accumulation.
|
|
220
7
|
*/
|
|
221
|
-
function
|
|
222
|
-
return (props) => {
|
|
223
|
-
const { classes } = resolveClasses(className, props || {}, config, systemPropMap, dynamicPropConfig);
|
|
224
|
-
return classes.join(" ");
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
//#endregion
|
|
228
|
-
//#region src/AnimusExtended.ts
|
|
229
|
-
function deepMerge$1(target, source) {
|
|
8
|
+
function deepMerge(target, source) {
|
|
230
9
|
const result = { ...target };
|
|
231
|
-
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
|
|
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]);
|
|
232
11
|
else result[key] = source[key];
|
|
233
12
|
return result;
|
|
234
13
|
}
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/AnimusExtended.ts
|
|
235
16
|
var AnimusExtendedWithAll = class {
|
|
236
17
|
propRegistry = {};
|
|
237
18
|
groupRegistry = {};
|
|
@@ -267,9 +48,6 @@ var AnimusExtendedWithAll = class {
|
|
|
267
48
|
asClass() {
|
|
268
49
|
return createClassResolver("", this._buildComponentConfig());
|
|
269
50
|
}
|
|
270
|
-
build() {
|
|
271
|
-
return Object.assign((() => ({})), { extend: this.extend.bind(this) });
|
|
272
|
-
}
|
|
273
51
|
_buildComponentConfig() {
|
|
274
52
|
const variantConfig = {};
|
|
275
53
|
for (const [key, vc] of Object.entries(this.variants)) {
|
|
@@ -290,12 +68,12 @@ var AnimusExtendedWithAll = class {
|
|
|
290
68
|
};
|
|
291
69
|
var AnimusExtendedWithSystem = class extends AnimusExtendedWithAll {
|
|
292
70
|
props(config) {
|
|
293
|
-
return new AnimusExtendedWithAll(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, this.statesConfig, this.activeGroups, deepMerge
|
|
71
|
+
return new AnimusExtendedWithAll(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, this.statesConfig, this.activeGroups, deepMerge({}, config), this.compounds);
|
|
294
72
|
}
|
|
295
73
|
};
|
|
296
74
|
var AnimusExtendedWithStates = class extends AnimusExtendedWithSystem {
|
|
297
|
-
|
|
298
|
-
return new AnimusExtendedWithSystem(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, this.statesConfig, deepMerge
|
|
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);
|
|
299
77
|
}
|
|
300
78
|
};
|
|
301
79
|
var AnimusExtendedWithCompounds = class AnimusExtendedWithCompounds extends AnimusExtendedWithStates {
|
|
@@ -306,7 +84,7 @@ var AnimusExtendedWithCompounds = class AnimusExtendedWithCompounds extends Anim
|
|
|
306
84
|
}]);
|
|
307
85
|
}
|
|
308
86
|
states(config) {
|
|
309
|
-
return new AnimusExtendedWithStates(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, deepMerge
|
|
87
|
+
return new AnimusExtendedWithStates(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, deepMerge(this.statesConfig, config), this.activeGroups, this.custom, this.compounds);
|
|
310
88
|
}
|
|
311
89
|
};
|
|
312
90
|
var AnimusExtendedWithVariants = class AnimusExtendedWithVariants extends AnimusExtendedWithCompounds {
|
|
@@ -318,31 +96,22 @@ var AnimusExtendedWithVariants = class AnimusExtendedWithVariants extends Animus
|
|
|
318
96
|
}
|
|
319
97
|
variant(options) {
|
|
320
98
|
const prop = options.prop || "variant";
|
|
321
|
-
return new AnimusExtendedWithVariants(this.propRegistry, this.groupRegistry, this.baseStyles, deepMerge
|
|
99
|
+
return new AnimusExtendedWithVariants(this.propRegistry, this.groupRegistry, this.baseStyles, deepMerge(this.variants, { [prop]: options }), this.statesConfig, this.activeGroups, this.custom, this.compounds);
|
|
322
100
|
}
|
|
323
101
|
};
|
|
324
102
|
var AnimusExtendedWithBase = class extends AnimusExtendedWithVariants {
|
|
325
103
|
variant(options) {
|
|
326
104
|
const prop = options.prop || "variant";
|
|
327
|
-
return new AnimusExtendedWithVariants(this.propRegistry, this.groupRegistry, this.baseStyles, deepMerge
|
|
105
|
+
return new AnimusExtendedWithVariants(this.propRegistry, this.groupRegistry, this.baseStyles, deepMerge(this.variants, { [prop]: options }), this.statesConfig, this.activeGroups, this.custom, this.compounds);
|
|
328
106
|
}
|
|
329
107
|
};
|
|
330
108
|
var AnimusExtended = class extends AnimusExtendedWithBase {
|
|
331
109
|
styles(config) {
|
|
332
|
-
return new AnimusExtendedWithBase(this.propRegistry, this.groupRegistry, deepMerge
|
|
110
|
+
return new AnimusExtendedWithBase(this.propRegistry, this.groupRegistry, deepMerge(this.baseStyles, config), this.variants, this.statesConfig, this.activeGroups, this.custom, this.compounds);
|
|
333
111
|
}
|
|
334
112
|
};
|
|
335
113
|
//#endregion
|
|
336
114
|
//#region src/Animus.ts
|
|
337
|
-
/**
|
|
338
|
-
* Deep merge utility — replaces lodash.merge for variant accumulation.
|
|
339
|
-
*/
|
|
340
|
-
function deepMerge(target, source) {
|
|
341
|
-
const result = { ...target };
|
|
342
|
-
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]);
|
|
343
|
-
else result[key] = source[key];
|
|
344
|
-
return result;
|
|
345
|
-
}
|
|
346
115
|
var AnimusWithAll = class {
|
|
347
116
|
propRegistry = {};
|
|
348
117
|
groupRegistry = {};
|
|
@@ -378,9 +147,6 @@ var AnimusWithAll = class {
|
|
|
378
147
|
asClass() {
|
|
379
148
|
return createClassResolver("", this._buildComponentConfig());
|
|
380
149
|
}
|
|
381
|
-
build() {
|
|
382
|
-
return Object.assign((() => ({})), { extend: this.extend.bind(this) });
|
|
383
|
-
}
|
|
384
150
|
_buildComponentConfig() {
|
|
385
151
|
const variantConfig = {};
|
|
386
152
|
for (const [key, vc] of Object.entries(this.variants)) {
|
|
@@ -411,7 +177,7 @@ var AnimusWithStates = class extends AnimusWithSystem {
|
|
|
411
177
|
constructor(props, groups, base, variants, states, compounds = []) {
|
|
412
178
|
super(props, groups, base, variants, states, {}, compounds);
|
|
413
179
|
}
|
|
414
|
-
|
|
180
|
+
system(config) {
|
|
415
181
|
return new AnimusWithSystem(this.propRegistry, this.groupRegistry, this.baseStyles, this.variants, this.statesConfig, config, this.compounds);
|
|
416
182
|
}
|
|
417
183
|
};
|
|
@@ -523,64 +289,78 @@ function compose(slots, options) {
|
|
|
523
289
|
return result;
|
|
524
290
|
}
|
|
525
291
|
//#endregion
|
|
526
|
-
//#region src/PropertyBuilder.ts
|
|
527
|
-
var PropertyBuilder = class PropertyBuilder {
|
|
528
|
-
#props;
|
|
529
|
-
#groups;
|
|
530
|
-
constructor(props, groups) {
|
|
531
|
-
this.#props = props || {};
|
|
532
|
-
this.#groups = groups || {};
|
|
533
|
-
}
|
|
534
|
-
addGroup(name, config) {
|
|
535
|
-
const newGroup = { [name]: Object.keys(config) };
|
|
536
|
-
return new PropertyBuilder({
|
|
537
|
-
...this.#props,
|
|
538
|
-
...config
|
|
539
|
-
}, {
|
|
540
|
-
...this.#groups,
|
|
541
|
-
...newGroup
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
|
-
build() {
|
|
545
|
-
return {
|
|
546
|
-
propRegistry: this.#props,
|
|
547
|
-
groupRegistry: this.#groups
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
};
|
|
551
|
-
//#endregion
|
|
552
292
|
//#region src/SystemBuilder.ts
|
|
553
293
|
var SystemBuilder = class SystemBuilder {
|
|
554
294
|
#propRegistry;
|
|
555
295
|
#groupRegistry;
|
|
556
|
-
|
|
557
|
-
constructor(propRegistry, groupRegistry, globalStyles) {
|
|
296
|
+
constructor(propRegistry, groupRegistry) {
|
|
558
297
|
this.#propRegistry = propRegistry || {};
|
|
559
298
|
this.#groupRegistry = groupRegistry || {};
|
|
560
|
-
this.#globalStyles = globalStyles;
|
|
561
299
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
300
|
+
addGroup(name, config) {
|
|
301
|
+
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.`);
|
|
302
|
+
for (const key of Object.keys(config)) if (key in this.#propRegistry) {
|
|
303
|
+
const existing = this.#propRegistry[key];
|
|
304
|
+
const incoming = config[key];
|
|
305
|
+
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)}".`);
|
|
306
|
+
}
|
|
307
|
+
const nextProps = {
|
|
308
|
+
...this.#propRegistry,
|
|
309
|
+
...config
|
|
310
|
+
};
|
|
311
|
+
const newGroup = { [name]: Object.keys(config) };
|
|
312
|
+
return new SystemBuilder(nextProps, {
|
|
313
|
+
...this.#groupRegistry,
|
|
314
|
+
...newGroup
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
addProps(config) {
|
|
318
|
+
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.`);
|
|
319
|
+
for (const key of Object.keys(config)) if (key in this.#propRegistry) {
|
|
320
|
+
const existing = this.#propRegistry[key];
|
|
321
|
+
const incoming = config[key];
|
|
322
|
+
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.`);
|
|
323
|
+
}
|
|
324
|
+
return new SystemBuilder({
|
|
325
|
+
...this.#propRegistry,
|
|
326
|
+
...config
|
|
327
|
+
}, this.#groupRegistry);
|
|
565
328
|
}
|
|
566
|
-
|
|
567
|
-
return
|
|
329
|
+
includes(_systems) {
|
|
330
|
+
return this;
|
|
568
331
|
}
|
|
569
332
|
build() {
|
|
570
333
|
const animus = new Animus(this.#propRegistry, this.#groupRegistry);
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
334
|
+
const propRegistry = this.#propRegistry;
|
|
335
|
+
const groupRegistry = this.#groupRegistry;
|
|
336
|
+
const system = Object.assign(animus, { toConfig: () => {
|
|
337
|
+
return serializeInstance(propRegistry, groupRegistry);
|
|
574
338
|
} });
|
|
339
|
+
const createGlobalStyles = (styles) => {
|
|
340
|
+
return {
|
|
341
|
+
__brand: "GlobalStyleBlock",
|
|
342
|
+
styles,
|
|
343
|
+
serialize(propConfig, transforms) {
|
|
344
|
+
return styles;
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
};
|
|
348
|
+
return {
|
|
349
|
+
system,
|
|
350
|
+
createGlobalStyles
|
|
351
|
+
};
|
|
575
352
|
}
|
|
576
353
|
};
|
|
577
|
-
function serializeInstance(propRegistry, groupRegistry
|
|
354
|
+
function serializeInstance(propRegistry, groupRegistry) {
|
|
578
355
|
const serialized = {};
|
|
579
356
|
const transforms = {};
|
|
580
357
|
for (const [propName, entry] of Object.entries(propRegistry)) {
|
|
581
358
|
const s = { property: entry.property };
|
|
582
359
|
if (entry.properties && entry.properties.length > 0) s.properties = [...entry.properties];
|
|
583
|
-
|
|
360
|
+
const scale = entry.scale;
|
|
361
|
+
if (typeof scale === "string") s.scale = scale;
|
|
362
|
+
else if (scale && typeof scale === "object") s.scale = scale;
|
|
363
|
+
if (entry.negative) s.negative = true;
|
|
584
364
|
if (entry.transform) {
|
|
585
365
|
const fn = entry.transform;
|
|
586
366
|
const name = fn.transformName ?? fn.name;
|
|
@@ -592,13 +372,11 @@ function serializeInstance(propRegistry, groupRegistry, globalStyles) {
|
|
|
592
372
|
if (entry.currentVar) s.currentVar = entry.currentVar;
|
|
593
373
|
serialized[propName] = s;
|
|
594
374
|
}
|
|
595
|
-
|
|
375
|
+
return {
|
|
596
376
|
propConfig: JSON.stringify(serialized),
|
|
597
377
|
groupRegistry: JSON.stringify(groupRegistry),
|
|
598
378
|
transforms
|
|
599
379
|
};
|
|
600
|
-
if (globalStyles) result.globalStyles = globalStyles;
|
|
601
|
-
return result;
|
|
602
380
|
}
|
|
603
381
|
function createSystem() {
|
|
604
382
|
return new SystemBuilder();
|
|
@@ -621,57 +399,46 @@ function merge(target, ...sources) {
|
|
|
621
399
|
}
|
|
622
400
|
return target;
|
|
623
401
|
}
|
|
624
|
-
|
|
402
|
+
/**
|
|
403
|
+
* Resolve a dot-path string against a nested object.
|
|
404
|
+
* walkDotPath({ gray: { 50: '#fafafa' } }, 'gray.50') → '#fafafa'
|
|
405
|
+
* The `_` identity key is handled: 'primary' resolves to obj.primary._ if obj.primary is an object with _.
|
|
406
|
+
*/
|
|
407
|
+
function walkDotPath(obj, path) {
|
|
408
|
+
const parts = path.split(".");
|
|
409
|
+
let current = obj;
|
|
410
|
+
for (const part of parts) {
|
|
411
|
+
if (!isObject(current)) return void 0;
|
|
412
|
+
current = current[part];
|
|
413
|
+
}
|
|
414
|
+
return current;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Flatten a nested object into a flat Record with dot-path keys.
|
|
418
|
+
* The `_` key is an identity marker — it produces the parent key without suffix.
|
|
419
|
+
* { gray: { 50: '#fafafa' } } → { 'gray.50': '#fafafa' }
|
|
420
|
+
* { primary: { _: 'ember', hover: 'x' } } → { 'primary': 'ember', 'primary.hover': 'x' }
|
|
421
|
+
* CSS variable names use dash-join, computed at the serialization boundary (not here).
|
|
422
|
+
*/
|
|
423
|
+
function flattenToDotPaths(object, path) {
|
|
625
424
|
const result = {};
|
|
626
|
-
for (const key of Object.keys(
|
|
425
|
+
for (const key of Object.keys(object)) {
|
|
426
|
+
const nextKey = path ? key === "_" ? path : `${path}.${key}` : key;
|
|
427
|
+
const current = object[key];
|
|
428
|
+
if (isObject(current)) Object.assign(result, flattenToDotPaths(current, nextKey));
|
|
429
|
+
else result[nextKey] = current;
|
|
430
|
+
}
|
|
627
431
|
return result;
|
|
628
432
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
...carry,
|
|
637
|
-
...flattenScale(current, nextKey)
|
|
638
|
-
};
|
|
639
|
-
return {
|
|
640
|
-
...carry,
|
|
641
|
-
[nextKey]: object[key]
|
|
642
|
-
};
|
|
643
|
-
}, {});
|
|
433
|
+
/**
|
|
434
|
+
* Convert a dot-path key to a dash-join key for CSS variable naming.
|
|
435
|
+
* 'gray.50' → 'gray-50'
|
|
436
|
+
* 'primary.hover' → 'primary-hover'
|
|
437
|
+
*/
|
|
438
|
+
function dotToDash(dotPath) {
|
|
439
|
+
return dotPath.replace(/\./g, "-");
|
|
644
440
|
}
|
|
645
441
|
//#endregion
|
|
646
|
-
//#region src/theme/serializeTokens.ts
|
|
647
|
-
const templateBreakpoints = (value, alias, theme) => {
|
|
648
|
-
if (isObject(value)) {
|
|
649
|
-
const { _, ...rest } = value;
|
|
650
|
-
const css = { [alias]: _ };
|
|
651
|
-
if (theme) {
|
|
652
|
-
const breakpoints = theme.breakpoints;
|
|
653
|
-
Object.keys(breakpoints).forEach((key) => {
|
|
654
|
-
if (rest[key]) css[breakpoints[key]] = { [alias]: rest[key] };
|
|
655
|
-
});
|
|
656
|
-
}
|
|
657
|
-
return css;
|
|
658
|
-
}
|
|
659
|
-
return { [alias]: value };
|
|
660
|
-
};
|
|
661
|
-
const serializeTokens = (tokens, prefix, theme) => {
|
|
662
|
-
const tokenReferences = {};
|
|
663
|
-
const tokenVariables = {};
|
|
664
|
-
Object.keys(tokens).forEach((key) => {
|
|
665
|
-
const varName = `--${prefix}-${key.replace("$", "")}`;
|
|
666
|
-
tokenReferences[key] = `var(${varName})`;
|
|
667
|
-
merge(tokenVariables, templateBreakpoints(tokens[key], varName, theme));
|
|
668
|
-
});
|
|
669
|
-
return {
|
|
670
|
-
tokens: tokenReferences,
|
|
671
|
-
variables: tokenVariables
|
|
672
|
-
};
|
|
673
|
-
};
|
|
674
|
-
//#endregion
|
|
675
442
|
//#region src/theme/createTheme.ts
|
|
676
443
|
const CSS_NAMED_COLORS = new Set([
|
|
677
444
|
"aliceblue",
|
|
@@ -846,13 +613,20 @@ function isValidCSSColor(value) {
|
|
|
846
613
|
if (CSS_NAMED_COLORS.has(v.toLowerCase())) return true;
|
|
847
614
|
return false;
|
|
848
615
|
}
|
|
849
|
-
/**
|
|
850
|
-
|
|
616
|
+
/**
|
|
617
|
+
* Validate that mode aliases reference existing color keys via dot-path traversal.
|
|
618
|
+
* Aliases are dot-path strings like 'gray.50' that must resolve in the nested color structure.
|
|
619
|
+
*/
|
|
620
|
+
function validateModeAliases(modeName, aliases, nestedColors, flatColorKeys, prefix) {
|
|
851
621
|
for (const [key, value] of Object.entries(aliases)) {
|
|
852
|
-
const aliasPath = prefix ? `${prefix}
|
|
853
|
-
if (
|
|
854
|
-
if (
|
|
855
|
-
|
|
622
|
+
const aliasPath = prefix ? `${prefix}.${key}` : key;
|
|
623
|
+
if (key === "_") {
|
|
624
|
+
if (typeof value === "string") {
|
|
625
|
+
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 ? ", ..." : ""}`);
|
|
626
|
+
} else if (isObject(value)) validateModeAliases(modeName, value, nestedColors, flatColorKeys, prefix);
|
|
627
|
+
} else if (typeof value === "string") {
|
|
628
|
+
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 ? ", ..." : ""}`);
|
|
629
|
+
} else if (isObject(value)) validateModeAliases(modeName, value, nestedColors, flatColorKeys, aliasPath);
|
|
856
630
|
}
|
|
857
631
|
}
|
|
858
632
|
/** Validate all color entries, throwing on invalid values. */
|
|
@@ -860,270 +634,274 @@ function validateColors(colors) {
|
|
|
860
634
|
for (const [key, value] of Object.entries(colors)) if (isObject(value)) validateColors(value);
|
|
861
635
|
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.`);
|
|
862
636
|
}
|
|
637
|
+
function createState(theme) {
|
|
638
|
+
return {
|
|
639
|
+
theme: theme || { breakpoints: {} },
|
|
640
|
+
emittedScales: /* @__PURE__ */ new Set(),
|
|
641
|
+
contextualVars: /* @__PURE__ */ new Map()
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
function copyState(state, nextTheme) {
|
|
645
|
+
const next = {
|
|
646
|
+
theme: nextTheme,
|
|
647
|
+
emittedScales: new Set(state.emittedScales),
|
|
648
|
+
contextualVars: /* @__PURE__ */ new Map()
|
|
649
|
+
};
|
|
650
|
+
for (const [scale, vars] of state.contextualVars) next.contextualVars.set(scale, [...vars]);
|
|
651
|
+
return next;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* ThemeScales — the final phase. Has addScale, extendScale, declareContextualVars, build.
|
|
655
|
+
* Also allows addColors and addColorModes for augmentation.
|
|
656
|
+
*/
|
|
863
657
|
var ThemeBuilder = class ThemeBuilder {
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
658
|
+
/** @internal */ _state;
|
|
659
|
+
constructor(state) {
|
|
660
|
+
this._state = state;
|
|
661
|
+
}
|
|
662
|
+
addBreakpoints(breakpoints) {
|
|
663
|
+
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)}`);
|
|
664
|
+
const nextTheme = merge({}, this._state.theme, { breakpoints });
|
|
665
|
+
return new ThemeBuilder(copyState(this._state, nextTheme));
|
|
666
|
+
}
|
|
667
|
+
from(builtTheme) {
|
|
668
|
+
const raw = {};
|
|
669
|
+
for (const key of Object.keys(builtTheme)) {
|
|
670
|
+
const val = builtTheme[key];
|
|
671
|
+
if (typeof val !== "function") raw[key] = val;
|
|
870
672
|
}
|
|
871
|
-
this
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
673
|
+
const nextTheme = merge({}, this._state.theme, raw);
|
|
674
|
+
const next = new ThemeBuilder(copyState(this._state, nextTheme));
|
|
675
|
+
const manifest = builtTheme.manifest;
|
|
676
|
+
if (manifest?.variableMap) for (const tokenPath of Object.keys(manifest.variableMap)) {
|
|
677
|
+
const scale = tokenPath.split(".")[0];
|
|
678
|
+
next._state.emittedScales.add(scale === "colors" ? "colors" : scale);
|
|
679
|
+
}
|
|
680
|
+
if (manifest?.contextualVars) for (const [scale, vars] of Object.entries(manifest.contextualVars)) next._state.contextualVars.set(scale, [...vars]);
|
|
878
681
|
return next;
|
|
879
682
|
}
|
|
880
|
-
/**
|
|
881
|
-
* @param colors A map of color tokens. Immediately converted to CSS variables `--color-${key}`.
|
|
882
|
-
* @example .addColors({ navy: 'navy', hyper: 'purple' })
|
|
883
|
-
*/
|
|
884
683
|
addColors(colors) {
|
|
885
684
|
validateColors(colors);
|
|
886
|
-
const
|
|
887
|
-
const
|
|
888
|
-
|
|
889
|
-
colors: tokens,
|
|
890
|
-
_variables: { root: variables },
|
|
891
|
-
_tokens: { colors: flatColors }
|
|
892
|
-
});
|
|
893
|
-
const next = this.#checkpoint(nextTheme);
|
|
894
|
-
next.#emittedScales.add("colors");
|
|
685
|
+
const nextTheme = merge({}, this._state.theme, { colors });
|
|
686
|
+
const next = new ThemeBuilder(copyState(this._state, nextTheme));
|
|
687
|
+
next._state.emittedScales.add("colors");
|
|
895
688
|
return next;
|
|
896
689
|
}
|
|
897
|
-
/**
|
|
898
|
-
* @param initialMode Default color mode key.
|
|
899
|
-
* @param modeConfig Map of color modes with semantic aliases pointing to palette keys.
|
|
900
|
-
* @example .addColorModes('dark', { dark: { primary: 'ember' }, light: { primary: 'void' } })
|
|
901
|
-
*/
|
|
902
690
|
addColorModes(initialMode, modeConfig) {
|
|
903
|
-
const
|
|
904
|
-
const
|
|
905
|
-
|
|
906
|
-
const
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
colors,
|
|
911
|
-
modes,
|
|
912
|
-
mode: initialMode,
|
|
913
|
-
_getColorValue: getColorValue,
|
|
914
|
-
_variables: { mode: variables },
|
|
915
|
-
_tokens: { modes: mapValues(modes, (mode) => mapValues(mode, getColorValue)) }
|
|
691
|
+
const nestedColors = this._state.theme.colors || {};
|
|
692
|
+
const flatColors = flattenToDotPaths(nestedColors);
|
|
693
|
+
const flatColorKeys = Object.keys(flatColors);
|
|
694
|
+
for (const [modeName, modeAliases] of Object.entries(modeConfig)) validateModeAliases(modeName, modeAliases, nestedColors, flatColorKeys, "");
|
|
695
|
+
const nextTheme = merge({}, this._state.theme, {
|
|
696
|
+
modes: modeConfig,
|
|
697
|
+
mode: initialMode
|
|
916
698
|
});
|
|
917
|
-
return this
|
|
699
|
+
return new ThemeBuilder(copyState(this._state, nextTheme));
|
|
918
700
|
}
|
|
919
|
-
/**
|
|
920
|
-
* Add a named scale to the theme.
|
|
921
|
-
*
|
|
922
|
-
* @param config.name - Scale name (e.g. 'space', 'sizes')
|
|
923
|
-
* @param config.values - Scale value map
|
|
924
|
-
* @param config.emit - When true, generates CSS variables (default: false)
|
|
925
|
-
*
|
|
926
|
-
* @example
|
|
927
|
-
* .addScale({ name: 'space', values: { 0: '0', 8: '0.5rem', 16: '1rem' } })
|
|
928
|
-
* .addScale({ name: 'sizes', emit: true, values: { navHeight: '48px' } })
|
|
929
|
-
*/
|
|
930
701
|
addScale(config) {
|
|
931
702
|
const { name, values, emit } = config;
|
|
932
|
-
const
|
|
933
|
-
|
|
934
|
-
if (emit)
|
|
935
|
-
const { variables, tokens } = serializeTokens(flattened, name, this.#theme);
|
|
936
|
-
nextTheme = merge({}, this.#theme, {
|
|
937
|
-
[name]: tokens,
|
|
938
|
-
_variables: { [name]: variables },
|
|
939
|
-
_tokens: { [name]: flattened }
|
|
940
|
-
});
|
|
941
|
-
} else nextTheme = merge({}, this.#theme, { [name]: flattened });
|
|
942
|
-
const next = this.#checkpoint(nextTheme);
|
|
943
|
-
if (emit) next.#emittedScales.add(name);
|
|
703
|
+
const nextTheme = merge({}, this._state.theme, { [name]: values });
|
|
704
|
+
const next = new ThemeBuilder(copyState(this._state, nextTheme));
|
|
705
|
+
if (emit) next._state.emittedScales.add(name);
|
|
944
706
|
return next;
|
|
945
707
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
* (`--{name}`) instead of token values. They cascade through the DOM like `currentColor`.
|
|
950
|
-
*
|
|
951
|
-
* @param vars - Object mapping scale names to arrays of contextual var names.
|
|
952
|
-
*
|
|
953
|
-
* @example
|
|
954
|
-
* .addContextualVars({
|
|
955
|
-
* colors: ['current-bg', 'current-border-color'],
|
|
956
|
-
* })
|
|
957
|
-
*/
|
|
958
|
-
addContextualVars(vars) {
|
|
959
|
-
for (const scale of Object.keys(vars)) if (!(scale in this.#theme)) throw new Error(`addContextualVars: scale '${scale}' not found — call addColors or addScale first`);
|
|
960
|
-
const next = this.#checkpoint(this.#theme);
|
|
708
|
+
declareContextualVars(vars) {
|
|
709
|
+
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`);
|
|
710
|
+
const next = new ThemeBuilder(copyState(this._state, this._state.theme));
|
|
961
711
|
for (const [scale, names] of Object.entries(vars)) {
|
|
962
|
-
const existing = next
|
|
963
|
-
next
|
|
712
|
+
const existing = next._state.contextualVars.get(scale) || [];
|
|
713
|
+
next._state.contextualVars.set(scale, [...existing, ...names]);
|
|
964
714
|
}
|
|
965
715
|
return next;
|
|
966
716
|
}
|
|
717
|
+
extendScale(key, updateFn) {
|
|
718
|
+
const nextTheme = merge({}, this._state.theme, { [key]: updateFn(this._state.theme[key]) });
|
|
719
|
+
return new ThemeBuilder(copyState(this._state, nextTheme));
|
|
720
|
+
}
|
|
967
721
|
/**
|
|
968
|
-
*
|
|
969
|
-
*
|
|
722
|
+
* Finalize the theme build.
|
|
723
|
+
* Flattens nested data at the boundary — produces manifest and serialize().
|
|
970
724
|
*/
|
|
971
|
-
updateScale(key, updateFn) {
|
|
972
|
-
const nextTheme = merge({}, this.#theme, { [key]: updateFn(this.#theme[key]) });
|
|
973
|
-
return this.#checkpoint(nextTheme);
|
|
974
|
-
}
|
|
975
|
-
/** Finalize the theme build. Returns the theme with a non-enumerable `.manifest` property. */
|
|
976
725
|
build() {
|
|
977
|
-
|
|
978
|
-
const
|
|
726
|
+
const theme = merge({}, this._state.theme);
|
|
727
|
+
const emittedScales = this._state.emittedScales;
|
|
728
|
+
const contextualVars = this._state.contextualVars;
|
|
729
|
+
const { tokenMap, variableMap, variables, modeVariables, modeTokens } = flattenTheme(theme, emittedScales);
|
|
730
|
+
resolveTokenRefs(tokenMap, variableMap, emittedScales);
|
|
731
|
+
const bpVariables = {};
|
|
732
|
+
if (theme.breakpoints && isObject(theme.breakpoints)) for (const [key, value] of Object.entries(theme.breakpoints)) bpVariables[`--breakpoint-${key}`] = `${value}px`;
|
|
979
733
|
let contextualVarsSerialized;
|
|
980
|
-
if (
|
|
734
|
+
if (contextualVars.size > 0) {
|
|
981
735
|
contextualVarsSerialized = {};
|
|
982
|
-
for (const [scale, vars] of
|
|
736
|
+
for (const [scale, vars] of contextualVars) contextualVarsSerialized[scale] = vars;
|
|
983
737
|
}
|
|
984
|
-
const
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
738
|
+
const variableCss = buildVariableCss(variables, bpVariables, modeVariables);
|
|
739
|
+
const manifest = {
|
|
740
|
+
tokenMap: {
|
|
741
|
+
...tokenMap,
|
|
742
|
+
...Object.fromEntries(Object.entries(theme.breakpoints || {}).map(([k, v]) => [`breakpoints.${k}`, String(v)]))
|
|
743
|
+
},
|
|
744
|
+
variableMap,
|
|
745
|
+
modes: modeTokens,
|
|
746
|
+
variableCss,
|
|
747
|
+
...contextualVarsSerialized ? { contextualVars: contextualVarsSerialized } : {}
|
|
748
|
+
};
|
|
990
749
|
Object.defineProperty(theme, "manifest", {
|
|
991
750
|
value: manifest,
|
|
992
751
|
enumerable: false,
|
|
993
752
|
configurable: false,
|
|
994
753
|
writable: false
|
|
995
754
|
});
|
|
755
|
+
Object.defineProperty(theme, "serialize", {
|
|
756
|
+
value: () => ({
|
|
757
|
+
scalesJson: JSON.stringify(manifest.tokenMap),
|
|
758
|
+
variableMapJson: JSON.stringify(manifest.variableMap),
|
|
759
|
+
variableCss: manifest.variableCss,
|
|
760
|
+
contextualVarsJson: JSON.stringify(manifest.contextualVars ?? {})
|
|
761
|
+
}),
|
|
762
|
+
enumerable: false,
|
|
763
|
+
configurable: false,
|
|
764
|
+
writable: false
|
|
765
|
+
});
|
|
766
|
+
Object.defineProperty(theme, "varRef", {
|
|
767
|
+
value: (tokenPath) => {
|
|
768
|
+
const varName = variableMap[tokenPath];
|
|
769
|
+
if (varName) return `var(${varName})`;
|
|
770
|
+
const dotIdx = tokenPath.indexOf(".");
|
|
771
|
+
if (dotIdx === -1) return void 0;
|
|
772
|
+
const scale = tokenPath.slice(0, dotIdx);
|
|
773
|
+
const key = tokenPath.slice(dotIdx + 1);
|
|
774
|
+
const scaleObj = theme[scale];
|
|
775
|
+
if (!isObject(scaleObj)) return void 0;
|
|
776
|
+
const val = walkDotPath(scaleObj, key);
|
|
777
|
+
return val !== void 0 ? String(val) : void 0;
|
|
778
|
+
},
|
|
779
|
+
enumerable: false,
|
|
780
|
+
configurable: false,
|
|
781
|
+
writable: false
|
|
782
|
+
});
|
|
996
783
|
return theme;
|
|
997
784
|
}
|
|
998
785
|
};
|
|
999
|
-
function createTheme(
|
|
1000
|
-
return new ThemeBuilder(
|
|
786
|
+
function createTheme() {
|
|
787
|
+
return new ThemeBuilder(createState());
|
|
1001
788
|
}
|
|
1002
|
-
/** Token ref pattern: {scale.key} */
|
|
789
|
+
/** Token ref pattern: {scale.key} or {scale.key.sub} */
|
|
1003
790
|
const TOKEN_REF_RE = /\{([^}]+)\}/g;
|
|
1004
791
|
/**
|
|
1005
|
-
*
|
|
1006
|
-
*
|
|
1007
|
-
* Runs once at build() time after all scales have been collected.
|
|
1008
|
-
*/
|
|
1009
|
-
function resolveThemeTokenRefs(theme, emittedScales) {
|
|
1010
|
-
for (const [scaleName, scaleValue] of Object.entries(theme)) {
|
|
1011
|
-
if (scaleName.startsWith("_")) continue;
|
|
1012
|
-
if (scaleName === "breakpoints" || scaleName === "mode" || scaleName === "modes") continue;
|
|
1013
|
-
if (typeof scaleValue === "function") continue;
|
|
1014
|
-
if (!isObject(scaleValue)) continue;
|
|
1015
|
-
for (const [key, value] of Object.entries(scaleValue)) {
|
|
1016
|
-
if (typeof value !== "string") continue;
|
|
1017
|
-
if (!value.includes("{")) continue;
|
|
1018
|
-
const resolved = value.replace(TOKEN_REF_RE, (match, ref) => {
|
|
1019
|
-
const dotIdx = ref.indexOf(".");
|
|
1020
|
-
if (dotIdx === -1) return match;
|
|
1021
|
-
const refScale = ref.slice(0, dotIdx);
|
|
1022
|
-
const refKey = ref.slice(dotIdx + 1);
|
|
1023
|
-
if (refScale === scaleName) {
|
|
1024
|
-
console.warn(`[animus] Self-referential token ref {${ref}} in scale '${scaleName}' — skipped`);
|
|
1025
|
-
return match;
|
|
1026
|
-
}
|
|
1027
|
-
const targetScale = theme[refScale];
|
|
1028
|
-
if (!targetScale || !isObject(targetScale)) {
|
|
1029
|
-
console.warn(`[animus] Token ref {${ref}} references unknown scale '${refScale}'`);
|
|
1030
|
-
return match;
|
|
1031
|
-
}
|
|
1032
|
-
const resolvedValue = targetScale[refKey];
|
|
1033
|
-
if (resolvedValue === void 0) {
|
|
1034
|
-
console.warn(`[animus] Token ref {${ref}} — key '${refKey}' not found in scale '${refScale}'`);
|
|
1035
|
-
return match;
|
|
1036
|
-
}
|
|
1037
|
-
return String(resolvedValue);
|
|
1038
|
-
});
|
|
1039
|
-
if (resolved !== value) {
|
|
1040
|
-
scaleValue[key] = resolved;
|
|
1041
|
-
if (theme._tokens?.[scaleName]) theme._tokens[scaleName][key] = resolved;
|
|
1042
|
-
if (theme._variables?.[scaleName]) {
|
|
1043
|
-
const varName = `--${scaleName}-${key.replace("$", "")}`;
|
|
1044
|
-
if (theme._variables[scaleName][varName] !== void 0) theme._variables[scaleName][varName] = resolved;
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
/**
|
|
1051
|
-
* Assemble a ThemeManifest from the built theme object.
|
|
1052
|
-
*
|
|
1053
|
-
* This is the single source of truth for the plugin — no string-matching
|
|
1054
|
-
* or re-flattening needed downstream.
|
|
792
|
+
* Flatten the nested theme into dot-path keyed token map and CSS variable declarations.
|
|
793
|
+
* This is the ONLY place where flattening happens.
|
|
1055
794
|
*/
|
|
1056
|
-
function
|
|
795
|
+
function flattenTheme(theme, emittedScales) {
|
|
1057
796
|
const tokenMap = {};
|
|
1058
797
|
const variableMap = {};
|
|
798
|
+
const variables = {};
|
|
799
|
+
const modeVariables = {};
|
|
800
|
+
const modeTokens = {};
|
|
1059
801
|
for (const [scaleName, scaleValue] of Object.entries(theme)) {
|
|
1060
802
|
if (scaleName.startsWith("_")) continue;
|
|
1061
803
|
if (scaleName === "breakpoints" || scaleName === "mode" || scaleName === "modes") continue;
|
|
1062
804
|
if (typeof scaleValue === "function") continue;
|
|
1063
|
-
if (isObject(scaleValue))
|
|
805
|
+
if (!isObject(scaleValue)) continue;
|
|
806
|
+
const flat = flattenToDotPaths(scaleValue);
|
|
807
|
+
const isEmitted = emittedScales.has(scaleName);
|
|
808
|
+
for (const [dotKey, rawValue] of Object.entries(flat)) {
|
|
809
|
+
const tokenPath = `${scaleName}.${dotKey}`;
|
|
810
|
+
const dashKey = dotToDash(dotKey);
|
|
811
|
+
const varName = `--${scaleName === "colors" ? "color" : scaleName}-${dashKey}`;
|
|
812
|
+
if (isEmitted) {
|
|
813
|
+
tokenMap[tokenPath] = `var(${varName})`;
|
|
814
|
+
variableMap[tokenPath] = varName;
|
|
815
|
+
variables[varName] = String(rawValue);
|
|
816
|
+
} else tokenMap[tokenPath] = String(rawValue);
|
|
817
|
+
}
|
|
1064
818
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
819
|
+
if (theme.modes && isObject(theme.modes) && theme.colors && isObject(theme.colors)) {
|
|
820
|
+
const flatColors = flattenToDotPaths(theme.colors);
|
|
821
|
+
for (const [modeName, modeAliases] of Object.entries(theme.modes)) {
|
|
822
|
+
if (!isObject(modeAliases)) continue;
|
|
823
|
+
const flatAliases = flattenToDotPaths(modeAliases);
|
|
824
|
+
const modeVars = {};
|
|
825
|
+
const modeVals = {};
|
|
826
|
+
for (const [aliasDotKey, colorRef] of Object.entries(flatAliases)) {
|
|
827
|
+
if (typeof colorRef !== "string") continue;
|
|
828
|
+
const varName = `--color-${dotToDash(aliasDotKey)}`;
|
|
829
|
+
const rawValue = flatColors[colorRef];
|
|
830
|
+
modeVals[`colors.${aliasDotKey}`] = rawValue !== void 0 ? String(rawValue) : String(colorRef);
|
|
831
|
+
modeVars[varName] = rawValue !== void 0 ? String(rawValue) : String(colorRef);
|
|
832
|
+
}
|
|
833
|
+
modeVariables[modeName] = modeVars;
|
|
834
|
+
modeTokens[modeName] = modeVals;
|
|
835
|
+
}
|
|
836
|
+
const initialMode = theme.mode;
|
|
837
|
+
if (initialMode && modeVariables[initialMode]) {
|
|
838
|
+
const initialModeVars = {};
|
|
839
|
+
const flatInitialAliases = flattenToDotPaths(theme.modes[initialMode]);
|
|
840
|
+
for (const [aliasDotKey, colorRef] of Object.entries(flatInitialAliases)) {
|
|
841
|
+
if (typeof colorRef !== "string") continue;
|
|
842
|
+
const varName = `--color-${dotToDash(aliasDotKey)}`;
|
|
843
|
+
const paletteVarName = variableMap[`colors.${colorRef}`];
|
|
844
|
+
if (paletteVarName) initialModeVars[varName] = `var(${paletteVarName})`;
|
|
845
|
+
tokenMap[`colors.${aliasDotKey}`] = `var(${varName})`;
|
|
846
|
+
variableMap[`colors.${aliasDotKey}`] = varName;
|
|
847
|
+
}
|
|
848
|
+
Object.assign(variables, initialModeVars);
|
|
849
|
+
}
|
|
1071
850
|
}
|
|
1072
851
|
return {
|
|
1073
852
|
tokenMap,
|
|
1074
853
|
variableMap,
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
854
|
+
variables,
|
|
855
|
+
modeVariables,
|
|
856
|
+
modeTokens
|
|
1078
857
|
};
|
|
1079
858
|
}
|
|
1080
|
-
/**
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
859
|
+
/**
|
|
860
|
+
* Resolve token refs ({scale.key}) in all flattened token values.
|
|
861
|
+
* Operates on the flattened tokenMap — does NOT mutate the nested theme.
|
|
862
|
+
*/
|
|
863
|
+
function resolveTokenRefs(tokenMap, variableMap, emittedScales) {
|
|
864
|
+
for (const [tokenPath, value] of Object.entries(tokenMap)) {
|
|
865
|
+
if (typeof value !== "string") continue;
|
|
866
|
+
if (!value.includes("{")) continue;
|
|
867
|
+
if (value.startsWith("var(")) continue;
|
|
868
|
+
const scaleName = tokenPath.split(".")[0];
|
|
869
|
+
const resolved = value.replace(TOKEN_REF_RE, (match, ref) => {
|
|
870
|
+
if (ref.split(".")[0] === scaleName) {
|
|
871
|
+
console.warn(`[animus] Self-referential token ref {${ref}} in scale '${scaleName}' — skipped`);
|
|
872
|
+
return match;
|
|
873
|
+
}
|
|
874
|
+
let lookupPath = ref;
|
|
875
|
+
let opacity;
|
|
876
|
+
const slashIdx = ref.indexOf("/");
|
|
877
|
+
if (slashIdx !== -1) {
|
|
878
|
+
lookupPath = ref.slice(0, slashIdx);
|
|
879
|
+
opacity = ref.slice(slashIdx + 1);
|
|
880
|
+
}
|
|
881
|
+
const refValue = tokenMap[lookupPath];
|
|
882
|
+
if (refValue === void 0) {
|
|
883
|
+
console.warn(`[animus] Token ref {${ref}} — path '${lookupPath}' not found in token map`);
|
|
884
|
+
return match;
|
|
885
|
+
}
|
|
886
|
+
if (refValue.startsWith("var(") && opacity) return refValue;
|
|
887
|
+
return opacity ? refValue : refValue;
|
|
888
|
+
});
|
|
889
|
+
if (resolved !== value) tokenMap[tokenPath] = resolved;
|
|
1099
890
|
}
|
|
1100
891
|
}
|
|
1101
|
-
/** Build CSS variable blocks from
|
|
1102
|
-
function
|
|
892
|
+
/** Build CSS variable blocks from flattened data. */
|
|
893
|
+
function buildVariableCss(rootVariables, breakpointVariables, modeVariables) {
|
|
1103
894
|
const parts = [];
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
}
|
|
1110
|
-
if (rootLines.length > 0) parts.push(`:root {\n${rootLines.join("\n")}\n}`);
|
|
1111
|
-
}
|
|
1112
|
-
if (theme._tokens?.modes && isObject(theme._tokens.modes)) for (const [modeName, modeTokens] of Object.entries(theme._tokens.modes)) {
|
|
1113
|
-
if (!isObject(modeTokens)) continue;
|
|
895
|
+
const rootLines = [];
|
|
896
|
+
for (const [varName, value] of Object.entries(rootVariables)) rootLines.push(` ${varName}: ${value};`);
|
|
897
|
+
for (const [varName, value] of Object.entries(breakpointVariables)) rootLines.push(` ${varName}: ${value};`);
|
|
898
|
+
if (rootLines.length > 0) parts.push(`:root {\n${rootLines.join("\n")}\n}`);
|
|
899
|
+
for (const [modeName, modeVars] of Object.entries(modeVariables)) {
|
|
1114
900
|
const modeLines = [];
|
|
1115
|
-
|
|
901
|
+
for (const [varName, value] of Object.entries(modeVars)) modeLines.push(` ${varName}: ${value};`);
|
|
1116
902
|
if (modeLines.length > 0) parts.push(`[data-color-mode="${modeName}"] {\n${modeLines.join("\n")}\n}`);
|
|
1117
903
|
}
|
|
1118
904
|
return parts.join("\n\n");
|
|
1119
905
|
}
|
|
1120
|
-
/** Recursively flatten mode tokens into CSS variable declaration lines. */
|
|
1121
|
-
function flattenModeTokensCss(lines, obj, prefix) {
|
|
1122
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
1123
|
-
const namePart = key === "_" ? prefix : prefix ? `${prefix}-${key}` : key;
|
|
1124
|
-
if (typeof value === "string" || typeof value === "number") lines.push(` --color-${namePart}: ${value};`);
|
|
1125
|
-
else if (isObject(value)) flattenModeTokensCss(lines, value, namePart);
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
906
|
//#endregion
|
|
1129
|
-
export { Animus, AnimusExtended, AnimusExtendedWithAll, AnimusWithAll,
|
|
907
|
+
export { Animus, AnimusExtended, AnimusExtendedWithAll, AnimusWithAll, SystemBuilder, ThemeBuilder, borderShorthand, compose, createClassResolver, createComponent, createScale, createSystem, createTheme, createTransform, gridItem, gridItemRatio, numericOrStringScale, numericScale, percentageOrAbsolute, size, stringScale };
|