@animus-ui/system 0.1.0-next.21 → 0.1.0-next.30
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.d.ts +1 -0
- package/dist/groups/index.d.ts.map +1 -1
- package/dist/groups/index.js +3 -2
- package/dist/index.d.ts +5 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +339 -527
- 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 +46 -53
- 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/serializeTokens.d.ts +2 -2
- package/dist/theme/serializeTokens.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/config.d.ts +11 -2
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/props.d.ts +13 -25
- package/dist/types/props.d.ts.map +1 -1
- package/dist/types/theme.d.ts +38 -19
- 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;
|
|
@@ -589,15 +369,14 @@ function serializeInstance(propRegistry, groupRegistry, globalStyles) {
|
|
|
589
369
|
transforms[name] = fn;
|
|
590
370
|
}
|
|
591
371
|
}
|
|
372
|
+
if (entry.currentVar) s.currentVar = entry.currentVar;
|
|
592
373
|
serialized[propName] = s;
|
|
593
374
|
}
|
|
594
|
-
|
|
375
|
+
return {
|
|
595
376
|
propConfig: JSON.stringify(serialized),
|
|
596
377
|
groupRegistry: JSON.stringify(groupRegistry),
|
|
597
378
|
transforms
|
|
598
379
|
};
|
|
599
|
-
if (globalStyles) result.globalStyles = globalStyles;
|
|
600
|
-
return result;
|
|
601
380
|
}
|
|
602
381
|
function createSystem() {
|
|
603
382
|
return new SystemBuilder();
|
|
@@ -620,57 +399,46 @@ function merge(target, ...sources) {
|
|
|
620
399
|
}
|
|
621
400
|
return target;
|
|
622
401
|
}
|
|
623
|
-
|
|
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) {
|
|
624
424
|
const result = {};
|
|
625
|
-
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
|
+
}
|
|
626
431
|
return result;
|
|
627
432
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
...carry,
|
|
636
|
-
...flattenScale(current, nextKey)
|
|
637
|
-
};
|
|
638
|
-
return {
|
|
639
|
-
...carry,
|
|
640
|
-
[nextKey]: object[key]
|
|
641
|
-
};
|
|
642
|
-
}, {});
|
|
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, "-");
|
|
643
440
|
}
|
|
644
441
|
//#endregion
|
|
645
|
-
//#region src/theme/serializeTokens.ts
|
|
646
|
-
const templateBreakpoints = (value, alias, theme) => {
|
|
647
|
-
if (isObject(value)) {
|
|
648
|
-
const { _, ...rest } = value;
|
|
649
|
-
const css = { [alias]: _ };
|
|
650
|
-
if (theme) {
|
|
651
|
-
const breakpoints = theme.breakpoints;
|
|
652
|
-
Object.keys(breakpoints).forEach((key) => {
|
|
653
|
-
if (rest[key]) css[breakpoints[key]] = { [alias]: rest[key] };
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
return css;
|
|
657
|
-
}
|
|
658
|
-
return { [alias]: value };
|
|
659
|
-
};
|
|
660
|
-
const serializeTokens = (tokens, prefix, theme) => {
|
|
661
|
-
const tokenReferences = {};
|
|
662
|
-
const tokenVariables = {};
|
|
663
|
-
Object.keys(tokens).forEach((key) => {
|
|
664
|
-
const varName = `--${prefix}-${key.replace("$", "")}`;
|
|
665
|
-
tokenReferences[key] = `var(${varName})`;
|
|
666
|
-
merge(tokenVariables, templateBreakpoints(tokens[key], varName, theme));
|
|
667
|
-
});
|
|
668
|
-
return {
|
|
669
|
-
tokens: tokenReferences,
|
|
670
|
-
variables: tokenVariables
|
|
671
|
-
};
|
|
672
|
-
};
|
|
673
|
-
//#endregion
|
|
674
442
|
//#region src/theme/createTheme.ts
|
|
675
443
|
const CSS_NAMED_COLORS = new Set([
|
|
676
444
|
"aliceblue",
|
|
@@ -845,13 +613,20 @@ function isValidCSSColor(value) {
|
|
|
845
613
|
if (CSS_NAMED_COLORS.has(v.toLowerCase())) return true;
|
|
846
614
|
return false;
|
|
847
615
|
}
|
|
848
|
-
/**
|
|
849
|
-
|
|
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) {
|
|
850
621
|
for (const [key, value] of Object.entries(aliases)) {
|
|
851
|
-
const aliasPath = prefix ? `${prefix}
|
|
852
|
-
if (
|
|
853
|
-
if (
|
|
854
|
-
|
|
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);
|
|
855
630
|
}
|
|
856
631
|
}
|
|
857
632
|
/** Validate all color entries, throwing on invalid values. */
|
|
@@ -859,237 +634,274 @@ function validateColors(colors) {
|
|
|
859
634
|
for (const [key, value] of Object.entries(colors)) if (isObject(value)) validateColors(value);
|
|
860
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.`);
|
|
861
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
|
+
*/
|
|
862
657
|
var ThemeBuilder = class ThemeBuilder {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
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;
|
|
672
|
+
}
|
|
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]);
|
|
872
681
|
return next;
|
|
873
682
|
}
|
|
874
|
-
/**
|
|
875
|
-
* @param colors A map of color tokens. Immediately converted to CSS variables `--color-${key}`.
|
|
876
|
-
* @example .addColors({ navy: 'navy', hyper: 'purple' })
|
|
877
|
-
*/
|
|
878
683
|
addColors(colors) {
|
|
879
684
|
validateColors(colors);
|
|
880
|
-
const
|
|
881
|
-
const
|
|
882
|
-
|
|
883
|
-
colors: tokens,
|
|
884
|
-
_variables: { root: variables },
|
|
885
|
-
_tokens: { colors: flatColors }
|
|
886
|
-
});
|
|
887
|
-
const next = this.#checkpoint(nextTheme);
|
|
888
|
-
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");
|
|
889
688
|
return next;
|
|
890
689
|
}
|
|
891
|
-
/**
|
|
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' } })
|
|
895
|
-
*/
|
|
896
690
|
addColorModes(initialMode, modeConfig) {
|
|
897
|
-
const
|
|
898
|
-
const
|
|
899
|
-
|
|
900
|
-
const
|
|
901
|
-
const
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
colors,
|
|
905
|
-
modes,
|
|
906
|
-
mode: initialMode,
|
|
907
|
-
_getColorValue: getColorValue,
|
|
908
|
-
_variables: { mode: variables },
|
|
909
|
-
_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
|
|
910
698
|
});
|
|
911
|
-
return this
|
|
699
|
+
return new ThemeBuilder(copyState(this._state, nextTheme));
|
|
912
700
|
}
|
|
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)
|
|
919
|
-
*
|
|
920
|
-
* @example
|
|
921
|
-
* .addScale({ name: 'space', values: { 0: '0', 8: '0.5rem', 16: '1rem' } })
|
|
922
|
-
* .addScale({ name: 'sizes', emit: true, values: { navHeight: '48px' } })
|
|
923
|
-
*/
|
|
924
701
|
addScale(config) {
|
|
925
702
|
const { name, values, emit } = config;
|
|
926
|
-
const
|
|
927
|
-
|
|
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);
|
|
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);
|
|
938
706
|
return next;
|
|
939
707
|
}
|
|
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));
|
|
711
|
+
for (const [scale, names] of Object.entries(vars)) {
|
|
712
|
+
const existing = next._state.contextualVars.get(scale) || [];
|
|
713
|
+
next._state.contextualVars.set(scale, [...existing, ...names]);
|
|
714
|
+
}
|
|
715
|
+
return next;
|
|
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
|
+
}
|
|
940
721
|
/**
|
|
941
|
-
*
|
|
942
|
-
*
|
|
722
|
+
* Finalize the theme build.
|
|
723
|
+
* Flattens nested data at the boundary — produces manifest and serialize().
|
|
943
724
|
*/
|
|
944
|
-
updateScale(key, updateFn) {
|
|
945
|
-
const nextTheme = merge({}, this.#theme, { [key]: updateFn(this.#theme[key]) });
|
|
946
|
-
return this.#checkpoint(nextTheme);
|
|
947
|
-
}
|
|
948
|
-
/** Finalize the theme build. Returns the theme with a non-enumerable `.manifest` property. */
|
|
949
725
|
build() {
|
|
950
|
-
|
|
951
|
-
const
|
|
952
|
-
const
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
}
|
|
956
|
-
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`;
|
|
733
|
+
let contextualVarsSerialized;
|
|
734
|
+
if (contextualVars.size > 0) {
|
|
735
|
+
contextualVarsSerialized = {};
|
|
736
|
+
for (const [scale, vars] of contextualVars) contextualVarsSerialized[scale] = vars;
|
|
737
|
+
}
|
|
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
|
+
};
|
|
957
749
|
Object.defineProperty(theme, "manifest", {
|
|
958
750
|
value: manifest,
|
|
959
751
|
enumerable: false,
|
|
960
752
|
configurable: false,
|
|
961
753
|
writable: false
|
|
962
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
|
+
});
|
|
963
783
|
return theme;
|
|
964
784
|
}
|
|
965
785
|
};
|
|
966
|
-
function createTheme(
|
|
967
|
-
return new ThemeBuilder(
|
|
786
|
+
function createTheme() {
|
|
787
|
+
return new ThemeBuilder(createState());
|
|
968
788
|
}
|
|
969
|
-
/** Token ref pattern: {scale.key} */
|
|
789
|
+
/** Token ref pattern: {scale.key} or {scale.key.sub} */
|
|
970
790
|
const TOKEN_REF_RE = /\{([^}]+)\}/g;
|
|
971
791
|
/**
|
|
972
|
-
*
|
|
973
|
-
*
|
|
974
|
-
* Runs once at build() time after all scales have been collected.
|
|
792
|
+
* Flatten the nested theme into dot-path keyed token map and CSS variable declarations.
|
|
793
|
+
* This is the ONLY place where flattening happens.
|
|
975
794
|
*/
|
|
976
|
-
function
|
|
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
|
-
}
|
|
1017
|
-
/**
|
|
1018
|
-
* Assemble a ThemeManifest from the built theme object.
|
|
1019
|
-
*
|
|
1020
|
-
* This is the single source of truth for the plugin — no string-matching
|
|
1021
|
-
* or re-flattening needed downstream.
|
|
1022
|
-
*/
|
|
1023
|
-
function assembleManifest(theme) {
|
|
795
|
+
function flattenTheme(theme, emittedScales) {
|
|
1024
796
|
const tokenMap = {};
|
|
1025
797
|
const variableMap = {};
|
|
798
|
+
const variables = {};
|
|
799
|
+
const modeVariables = {};
|
|
800
|
+
const modeTokens = {};
|
|
1026
801
|
for (const [scaleName, scaleValue] of Object.entries(theme)) {
|
|
1027
802
|
if (scaleName.startsWith("_")) continue;
|
|
1028
803
|
if (scaleName === "breakpoints" || scaleName === "mode" || scaleName === "modes") continue;
|
|
1029
804
|
if (typeof scaleValue === "function") continue;
|
|
1030
|
-
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
|
+
}
|
|
1031
818
|
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
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
|
+
}
|
|
1038
850
|
}
|
|
1039
851
|
return {
|
|
1040
852
|
tokenMap,
|
|
1041
853
|
variableMap,
|
|
1042
|
-
|
|
1043
|
-
|
|
854
|
+
variables,
|
|
855
|
+
modeVariables,
|
|
856
|
+
modeTokens
|
|
1044
857
|
};
|
|
1045
858
|
}
|
|
1046
|
-
/**
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
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;
|
|
1065
890
|
}
|
|
1066
891
|
}
|
|
1067
|
-
/** Build CSS variable blocks from
|
|
1068
|
-
function
|
|
892
|
+
/** Build CSS variable blocks from flattened data. */
|
|
893
|
+
function buildVariableCss(rootVariables, breakpointVariables, modeVariables) {
|
|
1069
894
|
const parts = [];
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
}
|
|
1076
|
-
if (rootLines.length > 0) parts.push(`:root {\n${rootLines.join("\n")}\n}`);
|
|
1077
|
-
}
|
|
1078
|
-
if (theme._tokens?.modes && isObject(theme._tokens.modes)) for (const [modeName, modeTokens] of Object.entries(theme._tokens.modes)) {
|
|
1079
|
-
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)) {
|
|
1080
900
|
const modeLines = [];
|
|
1081
|
-
|
|
901
|
+
for (const [varName, value] of Object.entries(modeVars)) modeLines.push(` ${varName}: ${value};`);
|
|
1082
902
|
if (modeLines.length > 0) parts.push(`[data-color-mode="${modeName}"] {\n${modeLines.join("\n")}\n}`);
|
|
1083
903
|
}
|
|
1084
904
|
return parts.join("\n\n");
|
|
1085
905
|
}
|
|
1086
|
-
/** Recursively flatten mode tokens into CSS variable declaration lines. */
|
|
1087
|
-
function flattenModeTokensCss(lines, obj, prefix) {
|
|
1088
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
1089
|
-
const namePart = key === "_" ? prefix : prefix ? `${prefix}-${key}` : key;
|
|
1090
|
-
if (typeof value === "string" || typeof value === "number") lines.push(` --color-${namePart}: ${value};`);
|
|
1091
|
-
else if (isObject(value)) flattenModeTokensCss(lines, value, namePart);
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
906
|
//#endregion
|
|
1095
|
-
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 };
|