@menapo/vicss 0.0.0-stage → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +662 -2
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1311 -0
- package/dist/config-loader-1kWS0cRU.js +162 -0
- package/dist/config-loader-CG76KA50.js +1 -0
- package/dist/config.d.ts +821 -0
- package/dist/config.js +2690 -0
- package/dist/index.d.ts +1528 -0
- package/dist/index.js +442 -0
- package/dist/lint.d.ts +114 -0
- package/dist/lint.js +0 -0
- package/dist/pool-F3qb30Fy.js +3389 -0
- package/dist/print-B_mn7wBu.js +5127 -0
- package/dist/snapshot-DAJ8FLU4.js +149 -0
- package/dist/theme-zBcr5DHi.js +2460 -0
- package/dist/types-file-D6Iu4kzv.js +2481 -0
- package/dist/vite.d.ts +270 -0
- package/dist/vite.js +2264 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +113 -0
- package/package.json +66 -3
|
@@ -0,0 +1,2460 @@
|
|
|
1
|
+
import { $ as isPaletteAtom, A as PROPERTIES, B as isCompound, E as compareChains, Et as mergeClassNames, Ft as hash, It as stableStringify, Lt as classes, M as longhandsOf, Mt as styleLiteral, Nt as toRecord, Ot as parseClassName, Rt as merge, V as isRootScope, _t as VicssError, a as modeFlag, bt as compareStrings, dt as isPlainObject, ft as isScaleName, gt as suggestTokens, i as exclusive, it as SCALES, kt as propertyKey, l as printAtoms, m as switchingTokens, mt as resolveReferences, o as modeVar, p as scopedModes, pt as lookupToken, r as classicValue, st as closestNames, ut as didYouMean, vt as malformed, w as canonicalChain, wt as makeClassName, xt as escapeClassName } from "./print-B_mn7wBu.js";
|
|
2
|
+
//#region src/core/location.ts
|
|
3
|
+
/**
|
|
4
|
+
* Offsets to line and column, for diagnostics and source maps. Lines end at
|
|
5
|
+
* `\n` only, as in Vite's code frames and in source-map consumers, so a CRLF
|
|
6
|
+
* file keeps its `\r` as the last column of each line.
|
|
7
|
+
*/
|
|
8
|
+
/** Returns a lookup from a UTF-16 offset to a 1-based line and a 0-based column. */
|
|
9
|
+
function createLocator(code) {
|
|
10
|
+
const starts = [0];
|
|
11
|
+
for (let i = code.indexOf("\n"); i !== -1; i = code.indexOf("\n", i + 1)) starts.push(i + 1);
|
|
12
|
+
return (offset) => {
|
|
13
|
+
const at = Math.max(0, Math.min(offset, code.length));
|
|
14
|
+
let lo = 0;
|
|
15
|
+
let hi = starts.length - 1;
|
|
16
|
+
while (lo < hi) {
|
|
17
|
+
const mid = lo + hi + 1 >> 1;
|
|
18
|
+
if (starts[mid] <= at) lo = mid;
|
|
19
|
+
else hi = mid - 1;
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
line: lo + 1,
|
|
23
|
+
column: at - starts[lo]
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/core/evaluate.ts
|
|
29
|
+
function isOpaque(value) {
|
|
30
|
+
return isDynamic(value) && value.opaque === true;
|
|
31
|
+
}
|
|
32
|
+
/** An opaque value for `node`: what the lint rules give an import. */
|
|
33
|
+
function opaqueAt(node) {
|
|
34
|
+
return {
|
|
35
|
+
__vxSpecial: "dynamic",
|
|
36
|
+
node,
|
|
37
|
+
opaque: true
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Objects and arrays an opaque spread or key left without some of their entries. */
|
|
41
|
+
const incomplete = /* @__PURE__ */ new WeakSet();
|
|
42
|
+
/**
|
|
43
|
+
* Whether an opaque spread or computed key left `value`, or any object or
|
|
44
|
+
* array inside it, incomplete. Values the call handler made (a compiled
|
|
45
|
+
* recipe, a marker) are not data: their insides are never searched.
|
|
46
|
+
*/
|
|
47
|
+
function isIncomplete(value, seen = /* @__PURE__ */ new Set()) {
|
|
48
|
+
if (typeof value !== "object" || value === null || seen.has(value)) return false;
|
|
49
|
+
if (incomplete.has(value) || isOpaque(value)) return true;
|
|
50
|
+
const proto = Object.getPrototypeOf(value);
|
|
51
|
+
if (!Array.isArray(value) && proto !== Object.prototype && proto !== null) return false;
|
|
52
|
+
seen.add(value);
|
|
53
|
+
if (isDynamic(value)) {
|
|
54
|
+
const { choice } = value;
|
|
55
|
+
return choice !== void 0 && [choice.consequent, choice.alternate].some((v) => isIncomplete(v, seen));
|
|
56
|
+
}
|
|
57
|
+
return Object.values(value).some((v) => isIncomplete(v, seen));
|
|
58
|
+
}
|
|
59
|
+
const NOT_HANDLED = Symbol("vicss.notHandled");
|
|
60
|
+
const STATIC_HINT = "Styles must be static. For a value known only at runtime, use a function style: css((color) => ({ color }))";
|
|
61
|
+
function isDynamic(value) {
|
|
62
|
+
return typeof value === "object" && value !== null && value.__vxSpecial === "dynamic";
|
|
63
|
+
}
|
|
64
|
+
/** Evaluates an expression. Throws VicssError `NOT_STATIC` naming the source that is not static. */
|
|
65
|
+
function evaluate(node, ctx) {
|
|
66
|
+
switch (node.type) {
|
|
67
|
+
case "Literal":
|
|
68
|
+
if ("regex" in node || "bigint" in node) throw notStatic(node, ctx);
|
|
69
|
+
return node.value;
|
|
70
|
+
case "TemplateLiteral": {
|
|
71
|
+
let out = cooked(node.quasis[0]);
|
|
72
|
+
const dynamic = [];
|
|
73
|
+
node.expressions.forEach((expression, i) => {
|
|
74
|
+
const value = evaluate(expression, ctx);
|
|
75
|
+
if (isDynamic(value)) {
|
|
76
|
+
dynamic.push(value);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (!isPrimitive(value)) throw notStatic(expression, ctx, "an object inside a string");
|
|
80
|
+
out += String(value) + cooked(node.quasis[i + 1]);
|
|
81
|
+
});
|
|
82
|
+
return dynamic.length > 0 ? dynamicAt(node, ...dynamic) : out;
|
|
83
|
+
}
|
|
84
|
+
case "Identifier": return identifier(node, ctx);
|
|
85
|
+
case "ObjectExpression": {
|
|
86
|
+
const out = {};
|
|
87
|
+
for (const property of node.properties) {
|
|
88
|
+
if (property.type === "SpreadElement") {
|
|
89
|
+
const value = evaluate(property.argument, ctx);
|
|
90
|
+
if (isOpaque(value)) {
|
|
91
|
+
for (const key of Object.keys(out)) out[key] = opaqueAt(property.argument);
|
|
92
|
+
incomplete.add(out);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (isDynamic(value)) throw notStatic(property.argument, ctx, "spreads a runtime value");
|
|
96
|
+
if (value === null || value === void 0) continue;
|
|
97
|
+
for (const [key, item] of Object.entries(value)) setKey(out, key, item, property, ctx);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (property.method || property.kind !== "init") throw notStatic(property, ctx, "methods and accessors are not static values");
|
|
101
|
+
let key;
|
|
102
|
+
if (property.computed) {
|
|
103
|
+
const value = evaluate(property.key, ctx);
|
|
104
|
+
if (isOpaque(value)) {
|
|
105
|
+
evaluate(property.value, ctx);
|
|
106
|
+
incomplete.add(out);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
key = keyFrom(value, property.key, ctx);
|
|
110
|
+
} else key = property.key.type === "Identifier" ? property.key.name : String(property.key.value);
|
|
111
|
+
setKey(out, key, evaluate(property.value, ctx), property, ctx);
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
case "ArrayExpression": {
|
|
116
|
+
const out = [];
|
|
117
|
+
for (const element of node.elements) if (element === null) out.push(void 0);
|
|
118
|
+
else if (element.type === "SpreadElement") {
|
|
119
|
+
const value = evaluate(element.argument, ctx);
|
|
120
|
+
if (isOpaque(value)) {
|
|
121
|
+
incomplete.add(out);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (!Array.isArray(value)) throw notStatic(element.argument, ctx, "spreads a value that is not an array");
|
|
125
|
+
out.push(...value);
|
|
126
|
+
} else out.push(evaluate(element, ctx));
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
case "UnaryExpression": {
|
|
130
|
+
const { operator } = node;
|
|
131
|
+
if (operator === "delete" || operator === "~") throw notStatic(node, ctx, `\`${operator}\` is not evaluated`);
|
|
132
|
+
const value = evaluate(node.argument, ctx);
|
|
133
|
+
if (isDynamic(value)) return dynamicAt(node, value);
|
|
134
|
+
if (operator === "!") return !value;
|
|
135
|
+
if (operator === "typeof") return typeof value;
|
|
136
|
+
if (operator === "void") return void 0;
|
|
137
|
+
if (!isPrimitive(value)) throw notStatic(node, ctx, "arithmetic on an object");
|
|
138
|
+
return operator === "-" ? -Number(value) : Number(value);
|
|
139
|
+
}
|
|
140
|
+
case "BinaryExpression": {
|
|
141
|
+
const { operator } = node;
|
|
142
|
+
if (!BINARY.has(operator)) throw notStatic(node, ctx, `\`${operator}\` is not evaluated`);
|
|
143
|
+
const left = evaluate(node.left, ctx);
|
|
144
|
+
const right = evaluate(node.right, ctx);
|
|
145
|
+
if (isDynamic(left) || isDynamic(right)) return dynamicAt(node, left, right);
|
|
146
|
+
const result = binary(operator, left, right);
|
|
147
|
+
if (result === void 0) throw notStatic(node, ctx, "an operand is an object");
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
case "LogicalExpression": {
|
|
151
|
+
const left = evaluate(node.left, ctx);
|
|
152
|
+
if (isDynamic(left)) {
|
|
153
|
+
const right = evaluate(node.right, ctx);
|
|
154
|
+
return choiceAt(node, left, node.operator === "&&" ? {
|
|
155
|
+
test: node.left,
|
|
156
|
+
consequent: right,
|
|
157
|
+
alternate: void 0
|
|
158
|
+
} : {
|
|
159
|
+
test: node.left,
|
|
160
|
+
nullish: node.operator === "??",
|
|
161
|
+
consequent: left,
|
|
162
|
+
alternate: right
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
switch (node.operator) {
|
|
166
|
+
case "&&": return left ? evaluate(node.right, ctx) : left;
|
|
167
|
+
case "||": return left ? left : evaluate(node.right, ctx);
|
|
168
|
+
default: return left ?? evaluate(node.right, ctx);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
case "ConditionalExpression": {
|
|
172
|
+
const test = evaluate(node.test, ctx);
|
|
173
|
+
if (isDynamic(test)) return choiceAt(node, test, {
|
|
174
|
+
test: node.test,
|
|
175
|
+
consequent: evaluate(node.consequent, ctx),
|
|
176
|
+
alternate: evaluate(node.alternate, ctx)
|
|
177
|
+
});
|
|
178
|
+
return evaluate(test ? node.consequent : node.alternate, ctx);
|
|
179
|
+
}
|
|
180
|
+
case "MemberExpression":
|
|
181
|
+
case "ChainExpression":
|
|
182
|
+
case "CallExpression": {
|
|
183
|
+
const value = chain(node, ctx);
|
|
184
|
+
return value === SHORT_CIRCUIT ? void 0 : value;
|
|
185
|
+
}
|
|
186
|
+
case "ParenthesizedExpression":
|
|
187
|
+
case "TSAsExpression":
|
|
188
|
+
case "TSSatisfiesExpression":
|
|
189
|
+
case "TSNonNullExpression":
|
|
190
|
+
case "TSTypeAssertion":
|
|
191
|
+
case "TSInstantiationExpression": return within(node, node.expression, ctx, evaluate);
|
|
192
|
+
default: throw notStatic(node, ctx);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Value of a binding, as a reference to it would evaluate: what the plugin's
|
|
197
|
+
* `resolveImport` uses to read another module's export. `at` locates errors.
|
|
198
|
+
*/
|
|
199
|
+
function evaluateBinding(binding, ctx, at) {
|
|
200
|
+
if (ctx.params?.has(binding)) return ctx.params.get(binding);
|
|
201
|
+
const node = at ?? binding.id ?? binding.node;
|
|
202
|
+
const name = `\`${binding.name}\``;
|
|
203
|
+
const unchanged = (value) => {
|
|
204
|
+
if (isPrimitive(value) || isCompiled(value)) return value;
|
|
205
|
+
const write = writeTo(binding, ctx);
|
|
206
|
+
if (write) {
|
|
207
|
+
const state = stateOf(ctx);
|
|
208
|
+
state.locate ??= createLocator(ctx.code);
|
|
209
|
+
const { line, column } = state.locate(write.node.start);
|
|
210
|
+
throw notStatic(node, ctx, `${name} is modified at ${line}:${column + 1}: \`${excerpt(ctx.code, write.node)}\``);
|
|
211
|
+
}
|
|
212
|
+
return value;
|
|
213
|
+
};
|
|
214
|
+
switch (binding.kind) {
|
|
215
|
+
case "import": return unchanged(memo(binding.node, node, binding, ctx, () => {
|
|
216
|
+
try {
|
|
217
|
+
return ctx.resolveImport(binding);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
if (error instanceof VicssError) throw error;
|
|
220
|
+
const detail = error instanceof Error && error.message ? `: ${error.message}` : "";
|
|
221
|
+
throw notStatic(node, ctx, `${name} is imported from "${binding.source}"${detail}`);
|
|
222
|
+
}
|
|
223
|
+
}));
|
|
224
|
+
case "const": {
|
|
225
|
+
const { init, node: declarator } = binding;
|
|
226
|
+
if (!init) throw notStatic(node, ctx, declarator.type === "TSEnumDeclaration" ? `${name} is an enum; use an \`as const\` object` : `${name} has no static initializer`);
|
|
227
|
+
const value = memo(init, node, binding, ctx, () => evaluate(init, ctx));
|
|
228
|
+
if (declarator.type !== "VariableDeclarator" || declarator.id.type === "Identifier") return unchanged(value);
|
|
229
|
+
const found = destructure(declarator.id, unchanged(value), binding.name, ctx);
|
|
230
|
+
return found === NOT_FOUND ? void 0 : found;
|
|
231
|
+
}
|
|
232
|
+
case "let":
|
|
233
|
+
case "var": throw notStatic(node, ctx, `${name} is declared with ${binding.kind}; only const is evaluated`);
|
|
234
|
+
case "param": throw notStatic(node, ctx, `${name} is a function parameter`);
|
|
235
|
+
case "function": throw notStatic(node, ctx, `${name} is a function`);
|
|
236
|
+
case "class": throw notStatic(node, ctx, `${name} is a class`);
|
|
237
|
+
case "catch": throw notStatic(node, ctx, `${name} is a caught exception`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/** Globals that read their arguments and never change them. */
|
|
241
|
+
const PURE_GLOBALS = /* @__PURE__ */ new Set([
|
|
242
|
+
"Object.freeze",
|
|
243
|
+
"Object.isFrozen",
|
|
244
|
+
"Object.keys",
|
|
245
|
+
"Object.values",
|
|
246
|
+
"Object.entries",
|
|
247
|
+
"Object.getOwnPropertyNames",
|
|
248
|
+
"JSON.stringify",
|
|
249
|
+
"Array.isArray",
|
|
250
|
+
"String",
|
|
251
|
+
"Number",
|
|
252
|
+
"Boolean",
|
|
253
|
+
"structuredClone"
|
|
254
|
+
]);
|
|
255
|
+
/**
|
|
256
|
+
* A write to `binding`'s object: through the binding itself, or through a
|
|
257
|
+
* const that may hold the same object. Within one module only: another
|
|
258
|
+
* module can still change an exported object, which no compiler can see.
|
|
259
|
+
*/
|
|
260
|
+
function writeTo(binding, ctx, seen = /* @__PURE__ */ new Set()) {
|
|
261
|
+
if (seen.has(binding)) return void 0;
|
|
262
|
+
seen.add(binding);
|
|
263
|
+
for (const write of ctx.scopes.writesOf(binding)) if (!write.call || !harmless(write.call, ctx)) return write;
|
|
264
|
+
for (const holder of ctx.scopes.holdersOf(binding)) {
|
|
265
|
+
const write = writeTo(holder, ctx, seen);
|
|
266
|
+
if (write) return write;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/** What a compiler call evaluates to, which no write to an object holding it can change. */
|
|
270
|
+
function isCompiled(value) {
|
|
271
|
+
const kind = value?.__vxSpecial;
|
|
272
|
+
return kind === "style" || kind === "marker" || kind === "recipe";
|
|
273
|
+
}
|
|
274
|
+
function harmless(call, ctx) {
|
|
275
|
+
if (ctx.isHarmlessCall?.(call)) return true;
|
|
276
|
+
const callee = call.callee;
|
|
277
|
+
let name;
|
|
278
|
+
if (callee.type === "Identifier") {
|
|
279
|
+
if (!ctx.scopes.bindingOf(callee)) name = callee.name;
|
|
280
|
+
} else if (callee.type === "MemberExpression" && !callee.computed && callee.object.type === "Identifier" && !ctx.scopes.bindingOf(callee.object)) name = `${callee.object.name}.${callee.property.name}`;
|
|
281
|
+
return name !== void 0 && (PURE_GLOBALS.has(name) || name.startsWith("console."));
|
|
282
|
+
}
|
|
283
|
+
const BINARY = /* @__PURE__ */ new Set([
|
|
284
|
+
"+",
|
|
285
|
+
"-",
|
|
286
|
+
"*",
|
|
287
|
+
"/",
|
|
288
|
+
"%",
|
|
289
|
+
"**",
|
|
290
|
+
"==",
|
|
291
|
+
"===",
|
|
292
|
+
"!=",
|
|
293
|
+
"!==",
|
|
294
|
+
"<",
|
|
295
|
+
"<=",
|
|
296
|
+
">",
|
|
297
|
+
">="
|
|
298
|
+
]);
|
|
299
|
+
const SHORT_CIRCUIT = Symbol("vicss.shortCircuit");
|
|
300
|
+
const NOT_FOUND = Symbol("vicss.notFound");
|
|
301
|
+
const states = /* @__PURE__ */ new WeakMap();
|
|
302
|
+
/** The node each NOT_STATIC error blames, so an enclosing member chain can take the blame instead. */
|
|
303
|
+
const blamed = /* @__PURE__ */ new WeakMap();
|
|
304
|
+
function stateOf(ctx) {
|
|
305
|
+
let state = states.get(ctx);
|
|
306
|
+
if (!state) states.set(ctx, state = {
|
|
307
|
+
values: /* @__PURE__ */ new Map(),
|
|
308
|
+
active: /* @__PURE__ */ new Set()
|
|
309
|
+
});
|
|
310
|
+
return state;
|
|
311
|
+
}
|
|
312
|
+
function memo(key, at, binding, ctx, compute) {
|
|
313
|
+
const { values, active } = stateOf(ctx);
|
|
314
|
+
if (values.has(key)) return values.get(key);
|
|
315
|
+
if (active.has(key)) throw notStatic(at, ctx, `\`${binding.name}\` refers to itself`);
|
|
316
|
+
active.add(key);
|
|
317
|
+
try {
|
|
318
|
+
const value = compute();
|
|
319
|
+
values.set(key, value);
|
|
320
|
+
return value;
|
|
321
|
+
} finally {
|
|
322
|
+
active.delete(key);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function identifier(id, ctx) {
|
|
326
|
+
const binding = ctx.scopes.bindingOf(id);
|
|
327
|
+
if (binding) return evaluateBinding(binding, ctx, id);
|
|
328
|
+
if (id.name === "undefined") return void 0;
|
|
329
|
+
throw notStatic(id, ctx, `\`${id.name}\` is not declared in this module`);
|
|
330
|
+
}
|
|
331
|
+
/** Members, calls and optional chains; `SHORT_CIRCUIT` ends a chain at a nullish `?.`. */
|
|
332
|
+
function chain(node, ctx) {
|
|
333
|
+
switch (node.type) {
|
|
334
|
+
case "ChainExpression": return chain(node.expression, ctx);
|
|
335
|
+
case "TSNonNullExpression": return within(node, node.expression, ctx, chain);
|
|
336
|
+
case "CallExpression": return call(node, ctx);
|
|
337
|
+
case "MemberExpression": {
|
|
338
|
+
const target = within(node, node.object, ctx, chain);
|
|
339
|
+
if (target === SHORT_CIRCUIT) return SHORT_CIRCUIT;
|
|
340
|
+
if (isDynamic(target)) return dynamicAt(node, target);
|
|
341
|
+
if (node.optional && (target === null || target === void 0)) return SHORT_CIRCUIT;
|
|
342
|
+
if (node.property.type === "PrivateIdentifier") throw notStatic(node, ctx);
|
|
343
|
+
let key;
|
|
344
|
+
if (node.computed) {
|
|
345
|
+
const value = evaluate(node.property, ctx);
|
|
346
|
+
if (isDynamic(value)) return dynamicAt(node, value);
|
|
347
|
+
key = keyFrom(value, node.property, ctx);
|
|
348
|
+
} else key = node.property.name;
|
|
349
|
+
return property(target, key, node, node.object, ctx);
|
|
350
|
+
}
|
|
351
|
+
default: return evaluate(node, ctx);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function call(node, ctx) {
|
|
355
|
+
if (ctx.callHandler) {
|
|
356
|
+
const root = rootIdentifier(node.callee);
|
|
357
|
+
const binding = root ? ctx.scopes.bindingOf(root) : void 0;
|
|
358
|
+
const value = ctx.callHandler(node.callee, binding, node.arguments, (n) => evaluate(n, ctx));
|
|
359
|
+
if (value !== NOT_HANDLED) return value;
|
|
360
|
+
}
|
|
361
|
+
throw notStatic(node, ctx);
|
|
362
|
+
}
|
|
363
|
+
function rootIdentifier(node) {
|
|
364
|
+
switch (node.type) {
|
|
365
|
+
case "Identifier": return node;
|
|
366
|
+
case "MemberExpression": return rootIdentifier(node.object);
|
|
367
|
+
case "ChainExpression":
|
|
368
|
+
case "ParenthesizedExpression":
|
|
369
|
+
case "TSAsExpression":
|
|
370
|
+
case "TSSatisfiesExpression":
|
|
371
|
+
case "TSNonNullExpression":
|
|
372
|
+
case "TSTypeAssertion":
|
|
373
|
+
case "TSInstantiationExpression": return rootIdentifier(node.expression);
|
|
374
|
+
default: return;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/** Own properties only: inherited ones are built-ins (`toUpperCase`, `map`) that the evaluator never runs. */
|
|
378
|
+
function property(target, key, node, object, ctx) {
|
|
379
|
+
if (target === null || target === void 0) throw notStatic(node, ctx, `\`${excerpt(ctx.code, object)}\` is ${String(target)}`);
|
|
380
|
+
const member = typeof target === "object" ? target.member : void 0;
|
|
381
|
+
if (typeof member === "function" && Object.hasOwn(target, "__vxSpecial")) try {
|
|
382
|
+
return member.call(target, key);
|
|
383
|
+
} catch (error) {
|
|
384
|
+
if (!(error instanceof VicssError) || error.loc) throw error;
|
|
385
|
+
const state = stateOf(ctx);
|
|
386
|
+
state.locate ??= createLocator(ctx.code);
|
|
387
|
+
throw new VicssError(error.code, `\`${excerpt(ctx.code, node)}\`: ${error.message}`, {
|
|
388
|
+
loc: {
|
|
389
|
+
file: ctx.file,
|
|
390
|
+
...state.locate(node.start)
|
|
391
|
+
},
|
|
392
|
+
hint: error.hint
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
const box = Object(target);
|
|
396
|
+
if (Object.hasOwn(box, key)) return box[key];
|
|
397
|
+
if (key in box) throw notStatic(node, ctx, `\`${key}\` is a built-in property`);
|
|
398
|
+
}
|
|
399
|
+
function keyOf(node, ctx) {
|
|
400
|
+
return keyFrom(evaluate(node, ctx), node, ctx);
|
|
401
|
+
}
|
|
402
|
+
function keyFrom(value, node, ctx) {
|
|
403
|
+
if (typeof value === "string") return value;
|
|
404
|
+
if (typeof value === "number") return String(value);
|
|
405
|
+
throw notStatic(node, ctx, "a property key must be a string or a number");
|
|
406
|
+
}
|
|
407
|
+
function setKey(out, key, value, node, ctx) {
|
|
408
|
+
if (key === "__proto__") throw notStatic(node, ctx, "`__proto__` keys are not supported");
|
|
409
|
+
out[key] = value;
|
|
410
|
+
}
|
|
411
|
+
/** The value `name` receives from destructuring `value` with `pattern`. */
|
|
412
|
+
function destructure(pattern, value, name, ctx) {
|
|
413
|
+
if (isOpaque(value)) return binds(pattern, name) ? value : NOT_FOUND;
|
|
414
|
+
switch (pattern.type) {
|
|
415
|
+
case "Identifier": return pattern.name === name ? value : NOT_FOUND;
|
|
416
|
+
case "AssignmentPattern":
|
|
417
|
+
if (!binds(pattern.left, name)) return NOT_FOUND;
|
|
418
|
+
return destructure(pattern.left, value === void 0 ? evaluate(pattern.right, ctx) : value, name, ctx);
|
|
419
|
+
case "ObjectPattern": {
|
|
420
|
+
if (value === null || value === void 0) throw notStatic(pattern, ctx, `destructures ${String(value)}`);
|
|
421
|
+
const taken = /* @__PURE__ */ new Set();
|
|
422
|
+
for (const prop of pattern.properties) {
|
|
423
|
+
if (prop.type === "RestElement") {
|
|
424
|
+
if (!binds(prop.argument, name)) continue;
|
|
425
|
+
const rest = {};
|
|
426
|
+
for (const [key, item] of Object.entries(value)) if (!taken.has(key)) rest[key] = item;
|
|
427
|
+
return destructure(prop.argument, rest, name, ctx);
|
|
428
|
+
}
|
|
429
|
+
const key = prop.computed ? keyOf(prop.key, ctx) : prop.key.type === "Identifier" ? prop.key.name : String(prop.key.value);
|
|
430
|
+
taken.add(key);
|
|
431
|
+
if (binds(prop.value, name)) return destructure(prop.value, property(value, key, prop, pattern, ctx), name, ctx);
|
|
432
|
+
}
|
|
433
|
+
return NOT_FOUND;
|
|
434
|
+
}
|
|
435
|
+
case "ArrayPattern":
|
|
436
|
+
if (!Array.isArray(value)) throw notStatic(pattern, ctx, "destructures a value that is not an array");
|
|
437
|
+
for (const [index, element] of pattern.elements.entries()) {
|
|
438
|
+
if (!element) continue;
|
|
439
|
+
if (element.type === "RestElement") {
|
|
440
|
+
if (binds(element.argument, name)) return destructure(element.argument, value.slice(index), name, ctx);
|
|
441
|
+
} else if (binds(element, name)) return destructure(element, value[index], name, ctx);
|
|
442
|
+
}
|
|
443
|
+
return NOT_FOUND;
|
|
444
|
+
default: return NOT_FOUND;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
function binds(pattern, name) {
|
|
448
|
+
switch (pattern.type) {
|
|
449
|
+
case "Identifier": return pattern.name === name;
|
|
450
|
+
case "AssignmentPattern": return binds(pattern.left, name);
|
|
451
|
+
case "RestElement": return binds(pattern.argument, name);
|
|
452
|
+
case "ObjectPattern": return pattern.properties.some((p) => binds(p.type === "RestElement" ? p : p.value, name));
|
|
453
|
+
case "ArrayPattern": return pattern.elements.some((e) => e !== null && binds(e, name));
|
|
454
|
+
default: return false;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* JS semantics on primitives. Objects take part only in strict equality and
|
|
459
|
+
* in loose equality with null; otherwise the result is `undefined`, which no
|
|
460
|
+
* operator produces, so the caller refuses.
|
|
461
|
+
*/
|
|
462
|
+
function binary(operator, left, right) {
|
|
463
|
+
if (operator === "===") return left === right;
|
|
464
|
+
if (operator === "!==") return left !== right;
|
|
465
|
+
if (!isPrimitive(left) || !isPrimitive(right)) {
|
|
466
|
+
const nullish = left === null || left === void 0 || right === null || right === void 0;
|
|
467
|
+
if (nullish && operator === "==") return left == right;
|
|
468
|
+
if (nullish && operator === "!=") return left != right;
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const strings = typeof left === "string" && typeof right === "string";
|
|
472
|
+
switch (operator) {
|
|
473
|
+
case "+": return typeof left === "string" || typeof right === "string" ? String(left) + String(right) : Number(left) + Number(right);
|
|
474
|
+
case "-": return Number(left) - Number(right);
|
|
475
|
+
case "*": return Number(left) * Number(right);
|
|
476
|
+
case "/": return Number(left) / Number(right);
|
|
477
|
+
case "%": return Number(left) % Number(right);
|
|
478
|
+
case "**": return Number(left) ** Number(right);
|
|
479
|
+
case "==": return left == right;
|
|
480
|
+
case "!=": return left != right;
|
|
481
|
+
case "<": return strings ? left < right : Number(left) < Number(right);
|
|
482
|
+
case "<=": return strings ? left <= right : Number(left) <= Number(right);
|
|
483
|
+
case ">": return strings ? left > right : Number(left) > Number(right);
|
|
484
|
+
default: return strings ? left >= right : Number(left) >= Number(right);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function isPrimitive(value) {
|
|
488
|
+
return value === null || typeof value !== "object";
|
|
489
|
+
}
|
|
490
|
+
function cooked(element) {
|
|
491
|
+
return element ? element.value.cooked ?? element.value.raw : "";
|
|
492
|
+
}
|
|
493
|
+
/** A runtime value for `node`, opaque when every runtime value it derives from is. */
|
|
494
|
+
function dynamicAt(node, ...from) {
|
|
495
|
+
const dynamic = from.filter(isDynamic);
|
|
496
|
+
return dynamic.length > 0 && dynamic.every(isOpaque) ? opaqueAt(node) : {
|
|
497
|
+
__vxSpecial: "dynamic",
|
|
498
|
+
node
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
/** A runtime test between known values; opaque when the test is. */
|
|
502
|
+
function choiceAt(node, test, choice) {
|
|
503
|
+
return isOpaque(test) ? {
|
|
504
|
+
__vxSpecial: "dynamic",
|
|
505
|
+
node,
|
|
506
|
+
choice,
|
|
507
|
+
opaque: true
|
|
508
|
+
} : {
|
|
509
|
+
__vxSpecial: "dynamic",
|
|
510
|
+
node,
|
|
511
|
+
choice
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Evaluates `inner` for `outer` (a wrapper or a member access). When `inner`
|
|
516
|
+
* itself is what is not static, `outer` takes the blame, so the message names
|
|
517
|
+
* `props.color` rather than `props`.
|
|
518
|
+
*/
|
|
519
|
+
function within(outer, inner, ctx, run) {
|
|
520
|
+
try {
|
|
521
|
+
return run(inner, ctx);
|
|
522
|
+
} catch (error) {
|
|
523
|
+
const blame = error instanceof VicssError ? blamed.get(error) : void 0;
|
|
524
|
+
if (blame?.node === inner) throw notStatic(outer, ctx, blame.reason);
|
|
525
|
+
throw error;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
/** The node a NOT_STATIC error names, for tools that report a range rather than a point. */
|
|
529
|
+
function blamedNode(error) {
|
|
530
|
+
return blamed.get(error)?.node;
|
|
531
|
+
}
|
|
532
|
+
function notStatic(node, ctx, reason) {
|
|
533
|
+
const state = stateOf(ctx);
|
|
534
|
+
state.locate ??= createLocator(ctx.code);
|
|
535
|
+
const error = new VicssError("NOT_STATIC", `\`${excerpt(ctx.code, node)}\` is not a static value${reason ? ` (${reason})` : ""}`, {
|
|
536
|
+
loc: {
|
|
537
|
+
file: ctx.file,
|
|
538
|
+
...state.locate(node.start)
|
|
539
|
+
},
|
|
540
|
+
hint: STATIC_HINT
|
|
541
|
+
});
|
|
542
|
+
blamed.set(error, {
|
|
543
|
+
node,
|
|
544
|
+
reason
|
|
545
|
+
});
|
|
546
|
+
return error;
|
|
547
|
+
}
|
|
548
|
+
const EXCERPT_LENGTH = 60;
|
|
549
|
+
function excerpt(code, node) {
|
|
550
|
+
const text = code.slice(node.start, node.end).replace(/\s+/g, " ");
|
|
551
|
+
return text.length > EXCERPT_LENGTH ? `${text.slice(0, 59)}…` : text;
|
|
552
|
+
}
|
|
553
|
+
//#endregion
|
|
554
|
+
//#region src/core/flatten.ts
|
|
555
|
+
/**
|
|
556
|
+
* Flattened recipe bases (PLAN D22, report 06 §9.2 #16): `recipe({ base,
|
|
557
|
+
* flatten: true })` compiles its base to one class, `vx-base_<hash>`, whose
|
|
558
|
+
* rule holds every declaration of the base, so an element repeated
|
|
559
|
+
* thousands of times (a table cell, a list row) carries one class for its
|
|
560
|
+
* base instead of one per declaration (Griffel's `makeResetStyles`).
|
|
561
|
+
*
|
|
562
|
+
* The rule prints one block per condition chain of the base, each in the
|
|
563
|
+
* chain's flattened sub-layer `vicss.a.<chain>.f`, declared right before the
|
|
564
|
+
* chain's atoms (order.ts). So, on one element:
|
|
565
|
+
*
|
|
566
|
+
* - an atom of the same chain beats the flattened declaration, whatever its
|
|
567
|
+
* tier, as the atom that clears it would in the atomic merge (`padding`
|
|
568
|
+
* after a base `paddingTop`), and as a longer key would by tier;
|
|
569
|
+
* - an atom of a higher chain beats it, and one of a lower chain loses to
|
|
570
|
+
* it, as between atoms: a caller's `background` leaves the base's `_hover`
|
|
571
|
+
* background in place (D4).
|
|
572
|
+
*
|
|
573
|
+
* That computes what the atomic base computes, under one condition: the
|
|
574
|
+
* base comes first among the element's styles, as a recipe's base does in
|
|
575
|
+
* its own result and as `sx(recipe(), …)` applies it. A flattened base
|
|
576
|
+
* clears nothing: what an earlier `sx()` argument sets stays above it,
|
|
577
|
+
* where the atomic base would have replaced it (the compiler warns for a
|
|
578
|
+
* folded call, the dev runtime for a runtime one).
|
|
579
|
+
*
|
|
580
|
+
* One exception keeps a declaration an atom: a property that wins over
|
|
581
|
+
* another of an unrelated key by tier alone (`paddingLeft` over
|
|
582
|
+
* `paddingInlineStart`, `borderTopLeftRadius` over
|
|
583
|
+
* `borderStartStartRadius`). The atomic cascade puts it a tier above its
|
|
584
|
+
* flow-relative twin; in the chain's flattened sub-layer it would sit below
|
|
585
|
+
* the twin and lose, and no other place both loses to the twin's own
|
|
586
|
+
* shorthand (`paddingInline`, which clears it) and beats the twin. A
|
|
587
|
+
* `colorPalette` stays an atom too: the sheet resets the other palettes'
|
|
588
|
+
* flags and recomputes the palette keys on its class (palettes.ts), which
|
|
589
|
+
* the flattened class would not carry.
|
|
590
|
+
*
|
|
591
|
+
* A flattened base's merge key is `vx-base`, shared by every flattened
|
|
592
|
+
* base: one per element, the last one, like themes (PLAN §4.8). Two bases'
|
|
593
|
+
* rules both set what their recipes set, in one sub-layer, and the sheet's
|
|
594
|
+
* order would pick between them rather than the call's.
|
|
595
|
+
*/
|
|
596
|
+
/** The merge key of every flattened base: one per element. */
|
|
597
|
+
const BASE_KEY = "vx-base";
|
|
598
|
+
function isBaseClass(cls) {
|
|
599
|
+
return cls.startsWith(`${BASE_KEY}_`);
|
|
600
|
+
}
|
|
601
|
+
let byKey;
|
|
602
|
+
/** `ltr|padding-left` → the properties that set it. */
|
|
603
|
+
let setters;
|
|
604
|
+
const twinCache = /* @__PURE__ */ new Map();
|
|
605
|
+
const related = (a, b) => a === b || a.startsWith(`${b}-`) || b.startsWith(`${a}-`);
|
|
606
|
+
/**
|
|
607
|
+
* Whether the property keyed `key` beats a property of an unrelated key
|
|
608
|
+
* that sets one of its longhands by tier alone (in either direction), or
|
|
609
|
+
* sits below one that does: in a flattened sub-layer it would lose to it.
|
|
610
|
+
*/
|
|
611
|
+
function winsByTier(key) {
|
|
612
|
+
let out = twinCache.get(key);
|
|
613
|
+
if (out !== void 0) return out;
|
|
614
|
+
if (!byKey || !setters) {
|
|
615
|
+
byKey = /* @__PURE__ */ new Map();
|
|
616
|
+
setters = /* @__PURE__ */ new Map();
|
|
617
|
+
for (const info of PROPERTIES.values()) {
|
|
618
|
+
if (info.expand || info.rejected) continue;
|
|
619
|
+
byKey.set(info.key, info);
|
|
620
|
+
for (const direction of ["ltr", "rtl"]) for (const longhand of longhandsOf(info.css, direction)) {
|
|
621
|
+
const id = `${direction}|${longhand}`;
|
|
622
|
+
setters.set(id, [...setters.get(id) ?? [], info]);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
const parent = key.lastIndexOf("-");
|
|
627
|
+
out = parent > 0 && winsByTier(key.slice(0, parent));
|
|
628
|
+
const info = byKey.get(key);
|
|
629
|
+
if (info && !out) {
|
|
630
|
+
for (const direction of ["ltr", "rtl"]) for (const longhand of longhandsOf(info.css, direction)) if ((setters.get(`${direction}|${longhand}`) ?? []).some((other) => other.tier < info.tier && !related(other.key, key))) out = true;
|
|
631
|
+
}
|
|
632
|
+
twinCache.set(key, out);
|
|
633
|
+
return out;
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Whether a base atom stays an atom when its base is flattened: a physical
|
|
637
|
+
* side or corner, which wins over its flow-relative twin by tier; a
|
|
638
|
+
* runtime value, which a recipe never holds; and a `colorPalette`, whose
|
|
639
|
+
* flag the sheet resets and recomputes the palette keys beside, on
|
|
640
|
+
* selectors of the palette atom's own class (palettes.ts).
|
|
641
|
+
*/
|
|
642
|
+
function staysAtomic(atom) {
|
|
643
|
+
return typeof atom.variable === "string" || isPaletteAtom(atom) || winsByTier(atom.key);
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* A base (one canonical class string's atoms) as one class and the atoms
|
|
647
|
+
* that stay; undefined when every declaration stays an atom. The class
|
|
648
|
+
* hashes the class names it stands for, which no config decides, so a
|
|
649
|
+
* library and the app that installs it name it alike (PLAN §5.13), and
|
|
650
|
+
* two recipes with the same base share it.
|
|
651
|
+
*/
|
|
652
|
+
function flattenBase(atoms, system) {
|
|
653
|
+
const flat = atoms.filter((atom) => !staysAtomic(atom));
|
|
654
|
+
if (flat.length === 0) return void 0;
|
|
655
|
+
const names = flat.map((atom) => atom.className).sort(compareStrings);
|
|
656
|
+
const className = `${BASE_KEY}_${hash(`base:${names.join(" ")}`)}`;
|
|
657
|
+
return {
|
|
658
|
+
className,
|
|
659
|
+
rule: baseRule(className, flat, system),
|
|
660
|
+
kept: atoms.filter(staysAtomic).map((atom) => atom.className)
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
/** The rule of the flattened base `id` over `atoms`, printed for `system`. */
|
|
664
|
+
function baseRule(id, atoms, system) {
|
|
665
|
+
const sorted = [...atoms].sort((a, b) => compareStrings(a.className, b.className));
|
|
666
|
+
const tokens = /* @__PURE__ */ new Map();
|
|
667
|
+
for (const atom of sorted) for (const ref of atom.tokens) tokens.set(`${ref.scale}:${ref.path}`, ref);
|
|
668
|
+
const rule = {
|
|
669
|
+
id,
|
|
670
|
+
kind: "base",
|
|
671
|
+
css: "",
|
|
672
|
+
tokens: [...tokens.values()],
|
|
673
|
+
atoms: sorted
|
|
674
|
+
};
|
|
675
|
+
return {
|
|
676
|
+
...rule,
|
|
677
|
+
css: printAtoms([], system, [rule])
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
/** The atoms the flattened bases among `rules` stand for, one per class name. */
|
|
681
|
+
function baseAtoms(rules) {
|
|
682
|
+
const out = /* @__PURE__ */ new Map();
|
|
683
|
+
for (const rule of rules) {
|
|
684
|
+
if (rule.kind !== "base") continue;
|
|
685
|
+
for (const atom of rule.atoms ?? []) if (!out.has(atom.className)) out.set(atom.className, atom);
|
|
686
|
+
}
|
|
687
|
+
return [...out.values()];
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* One class, one base: two flattened bases whose atoms hash to one name
|
|
691
|
+
* (across files, or between an app and its libraries). Their atoms are
|
|
692
|
+
* checked with every other atom by `checkAtoms`.
|
|
693
|
+
*/
|
|
694
|
+
function checkBases(rules) {
|
|
695
|
+
const seen = /* @__PURE__ */ new Map();
|
|
696
|
+
const errors = [];
|
|
697
|
+
for (const rule of rules) {
|
|
698
|
+
if (rule.kind !== "base") continue;
|
|
699
|
+
const names = (rule.atoms ?? []).map((atom) => atom.className).join(" ");
|
|
700
|
+
const first = seen.get(rule.id);
|
|
701
|
+
if (first === void 0) seen.set(rule.id, names);
|
|
702
|
+
else if (first !== names) errors.push(new VicssError("CLASS_COLLISION", `the flattened base \`${rule.id}\` stands for both \`${first}\` and \`${names}\``, { hint: "Two bases hash to the same name: write one of them slightly differently." }));
|
|
703
|
+
}
|
|
704
|
+
return errors;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Where a merge of `args` (class strings, in order) computes otherwise with
|
|
708
|
+
* the flattened base the element keeps than with its atoms: the classes of
|
|
709
|
+
* earlier arguments that the base's atoms would clear, or beat by tier (a
|
|
710
|
+
* base `paddingTop` after an earlier `padding`), and its rule, applied
|
|
711
|
+
* under them, cannot. `atomsOf` names a base's atoms by class. Undefined
|
|
712
|
+
* when the two merges keep the same classes, and so compute alike.
|
|
713
|
+
*/
|
|
714
|
+
function shadowedByEarlier(args, atomsOf) {
|
|
715
|
+
const lists = args.map((arg) => arg.split(" ").filter(Boolean));
|
|
716
|
+
let at = -1;
|
|
717
|
+
let base;
|
|
718
|
+
for (const [i, list] of lists.entries()) for (const cls of list) if (isBaseClass(cls)) {
|
|
719
|
+
at = i;
|
|
720
|
+
base = cls;
|
|
721
|
+
}
|
|
722
|
+
const atoms = base === void 0 ? void 0 : atomsOf(base);
|
|
723
|
+
if (base === void 0 || atoms === void 0 || at === 0) return void 0;
|
|
724
|
+
const without = lists.map((list) => list.filter((cls) => !isBaseClass(cls)).join(" "));
|
|
725
|
+
const own = mergeClassNames([atoms.join(" "), without[at]]).join(" ");
|
|
726
|
+
const atomic = new Set(mergeClassNames([
|
|
727
|
+
...without.slice(0, at),
|
|
728
|
+
own,
|
|
729
|
+
...without.slice(at + 1)
|
|
730
|
+
]));
|
|
731
|
+
const flattened = new Set(mergeClassNames([atoms.join(" "), ...without]));
|
|
732
|
+
const lost = atoms.filter((cls) => atomic.has(cls) && !flattened.has(cls)).map(parseClassName);
|
|
733
|
+
const classes = /* @__PURE__ */ new Set();
|
|
734
|
+
for (const cls of without.slice(0, at).flatMap((arg) => arg.split(" ").filter(Boolean))) {
|
|
735
|
+
const { chain, key } = parseClassName(cls);
|
|
736
|
+
if (lost.some((atom) => atom.chain === chain && (atom.key === key || atom.key.startsWith(`${key}-`))) || flattened.has(cls) && !atomic.has(cls)) classes.add(cls);
|
|
737
|
+
}
|
|
738
|
+
return classes.size > 0 ? {
|
|
739
|
+
base,
|
|
740
|
+
classes: [...classes].sort(compareStrings)
|
|
741
|
+
} : void 0;
|
|
742
|
+
}
|
|
743
|
+
//#endregion
|
|
744
|
+
//#region src/runtime/style.ts
|
|
745
|
+
/**
|
|
746
|
+
* One compiled style as a class (PLAN §5.6, §5.16): `class={card}`,
|
|
747
|
+
* `className={card}` and `${card}` read it as its class attribute. A
|
|
748
|
+
* compiled style keeps its shape, merge id → class, so `sx()` still merges
|
|
749
|
+
* it; the string comes from the prototype every compiled style shares. A
|
|
750
|
+
* class method is not enumerable, so the merge's `for…in`, `Object.keys`
|
|
751
|
+
* and a spread never see it, and a constant carries no function of its own.
|
|
752
|
+
*/
|
|
753
|
+
var Styled = class {
|
|
754
|
+
/**
|
|
755
|
+
* The class attribute `sx(style)` sets, by `sx()`'s own merge and cache:
|
|
756
|
+
* the same string by construction, a cache hit after the first render,
|
|
757
|
+
* and 31 B less than a merge of its own beside `sx()`, which nearly every
|
|
758
|
+
* app ships (PLAN §6).
|
|
759
|
+
*/
|
|
760
|
+
toString() {
|
|
761
|
+
return classes([this], {});
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
/** What the compiler wraps a compiled style literal in (`__vxStyled({…})`). */
|
|
765
|
+
const styled = (record) => Object.setPrototypeOf(record, Styled.prototype);
|
|
766
|
+
//#endregion
|
|
767
|
+
//#region src/runtime/recipe.ts
|
|
768
|
+
/**
|
|
769
|
+
* The recipe resolver (PLAN §4.4, §5.7): what a compiled `recipe()` becomes,
|
|
770
|
+
* `__vxRecipe(table)`, or `__vxRecipe(table, __vxResponsive)` for a recipe
|
|
771
|
+
* with responsive variants. The compiler did the styling: every cell of the
|
|
772
|
+
* table is a compiled style (merge id → class), so a call chooses cells and
|
|
773
|
+
* merges them with `sx`'s rule, in the order a style object would be assigned: base,
|
|
774
|
+
* each variant in declaration order, then the compounds that hold. Literal
|
|
775
|
+
* calls fold at build time through `resolveRecipe` in src/core/recipe.ts,
|
|
776
|
+
* which must agree with this file class for class. Bytes matter more than
|
|
777
|
+
* elegance: tests/runtime/size.test.ts holds both functions to a budget, and
|
|
778
|
+
* an app without responsive recipes never bundles `__vxResponsive`.
|
|
779
|
+
*
|
|
780
|
+
* The table (`RecipeTable` below; src/core/recipe.ts writes it):
|
|
781
|
+
*
|
|
782
|
+
* - `k`: variant names in declaration order; `s`: slot names, in slot recipes.
|
|
783
|
+
* - `b`, `v[variant][value]`: base and variant styles. A cell is a style,
|
|
784
|
+
* or a style per slot in a slot recipe.
|
|
785
|
+
* - `c`: compounds in order, `[when, classes, ranges?, cut?]`; `when` lists
|
|
786
|
+
* the values each named variant must have, as strings (`"true"`).
|
|
787
|
+
* - `d`: defaults as strings, `"false"` for a boolean variant without one.
|
|
788
|
+
* - `q`: `[conditions, breakpoints]`, the conditions a responsive prop may
|
|
789
|
+
* set (`md`, `containerMd`) in ascending rank, of which the first
|
|
790
|
+
* `breakpoints` are breakpoints and the rest container sizes.
|
|
791
|
+
* - `r[variant][value][condition]`: a responsive variant's value under each
|
|
792
|
+
* of those conditions. Every responsive variant has an entry, in
|
|
793
|
+
* declaration order.
|
|
794
|
+
* - `ranges[piece]`: a compound under the range condition of each piece of
|
|
795
|
+
* the width axis, keyed by its bounds (`",md"` below md, `"md,lg"`, `"lg,"`
|
|
796
|
+
* from lg, `"containerMd,"`); `cut` lists the responsive variants whose
|
|
797
|
+
* conditions cut the axis into the pieces it is evaluated on.
|
|
798
|
+
* - `f`, in dev only: each flattened base's atoms, as a style, by its class
|
|
799
|
+
* (the cell's `$b`, src/core/flatten.ts), for `flatBases`.
|
|
800
|
+
*/
|
|
801
|
+
const warn = (message) => console.warn("vicss: " + message);
|
|
802
|
+
/**
|
|
803
|
+
* Dev only: the atoms of every flattened base a resolver was made with, by
|
|
804
|
+
* class, which `sx()`'s dev variant reads (runtime/dev.ts). Production
|
|
805
|
+
* builds drop it with the branch that fills it.
|
|
806
|
+
*/
|
|
807
|
+
const flatBases = {};
|
|
808
|
+
const __vxRecipe = (t, x) => {
|
|
809
|
+
const { k: keys, d: defaults, v: variants } = t;
|
|
810
|
+
const memo = /* @__PURE__ */ new Map();
|
|
811
|
+
if (process.env.NODE_ENV != "production") Object.assign(flatBases, t.f);
|
|
812
|
+
return Object.assign((props = {}) => {
|
|
813
|
+
const id = JSON.stringify(keys.map((key) => props[key] ?? defaults[key]));
|
|
814
|
+
let result = memo.get(id);
|
|
815
|
+
if (!result) {
|
|
816
|
+
const values = {};
|
|
817
|
+
const cells = [t.b];
|
|
818
|
+
for (const key of keys) {
|
|
819
|
+
let value = props[key] ?? defaults[key];
|
|
820
|
+
if (typeof value == "object") {
|
|
821
|
+
if (process.env.NODE_ENV != "production" && !t.r?.[key]) warn(`\`${key}\` is not a responsive variant; its base value applies.`);
|
|
822
|
+
value = value.base ?? defaults[key];
|
|
823
|
+
}
|
|
824
|
+
cells.push(variants[key][values[key] = "" + value]);
|
|
825
|
+
}
|
|
826
|
+
const add = x?.(t, props, values, cells);
|
|
827
|
+
for (const compound of t.c) if (add) add(compound);
|
|
828
|
+
else if (Object.keys(compound[0]).every((key) => compound[0][key].includes(values[key]))) cells.push(compound[1]);
|
|
829
|
+
const slot = (name) => styled(Object.fromEntries(merge(cells.map((cell) => typeof cell == "object" && (name ? cell[name] : cell)))[1]));
|
|
830
|
+
result = t.s ? Object.fromEntries(t.s.map((name) => [name, slot(name)])) : slot();
|
|
831
|
+
if (process.env.NODE_ENV != "production") Object.freeze(result);
|
|
832
|
+
if (memo.size >> 10) memo.clear();
|
|
833
|
+
memo.set(id, result);
|
|
834
|
+
}
|
|
835
|
+
return result;
|
|
836
|
+
}, {
|
|
837
|
+
variantKeys: keys,
|
|
838
|
+
splitProps: (props) => {
|
|
839
|
+
const split = [{}, {}];
|
|
840
|
+
for (const key in props) split[keys.includes(key) ? 0 : 1][key] = props[key];
|
|
841
|
+
return split;
|
|
842
|
+
},
|
|
843
|
+
schema: {
|
|
844
|
+
variants: Object.fromEntries(keys.map((key) => [key, Object.keys(variants[key])])),
|
|
845
|
+
defaults
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
};
|
|
849
|
+
/**
|
|
850
|
+
* Responsive props (PLAN §4.4): each condition a prop sets adds its value's
|
|
851
|
+
* `r` cell, in rank order, so values stack mobile-first. A compound that has
|
|
852
|
+
* ranges is evaluated on each piece of the width axis, cut at the conditions
|
|
853
|
+
* its `cut` variants' props set, with the values in effect there, and adds
|
|
854
|
+
* its classes under the range condition of each piece where it holds: that
|
|
855
|
+
* range outranks every condition that holds anywhere in the piece. `ranges`
|
|
856
|
+
* keys a piece by its bounds: `",md"` below md, `"md,lg"`, `"lg,"` from lg.
|
|
857
|
+
*/
|
|
858
|
+
const __vxResponsive = (t, props, values, cells) => {
|
|
859
|
+
const [all, breakpoints] = t.q;
|
|
860
|
+
const r = t.r;
|
|
861
|
+
/** The value `key`'s prop sets under condition `name`; `key` is a responsive variant. */
|
|
862
|
+
const at = (key, name) => props[key]?.["_" + name];
|
|
863
|
+
for (const key in r) {
|
|
864
|
+
if (process.env.NODE_ENV != "production" && typeof props[key] == "object") {
|
|
865
|
+
for (const name in props[key]) if (name != "base" && !(name[0] == "_" && all.includes(name.slice(1)))) warn(`\`${name}\` is not a breakpoint or container condition of \`${key}\`; it is ignored. A recipe takes those of the design system it was compiled with: a library's, its own.`);
|
|
866
|
+
}
|
|
867
|
+
for (const name of all) if (at(key, name) != null) cells.push(r[key][at(key, name)]?.[name]);
|
|
868
|
+
}
|
|
869
|
+
return ([when, classes, ranges, cut = []]) => {
|
|
870
|
+
const current = { ...values };
|
|
871
|
+
const points = all.filter((name) => cut.some((key) => at(key, name) != null));
|
|
872
|
+
const n = points.length;
|
|
873
|
+
const held = ["", ...points].map((point) => (cut.map((key) => current[key] = "" + (at(key, point) ?? current[key])), Object.keys(when).every((key) => when[key].includes(current[key]))));
|
|
874
|
+
if (!n) return held[0] && cells.push(classes);
|
|
875
|
+
if (all.indexOf(points[n - 1]) < breakpoints) return held.map((holds, i) => holds && cells.push(ranges[[points[i - 1], points[i]] + ""]));
|
|
876
|
+
let i = n;
|
|
877
|
+
if (all.indexOf(points[0]) >= breakpoints) for (; i >= 0 && held[i]; i--) cells.push(i ? ranges[points[i - 1] + ","] : classes);
|
|
878
|
+
if (process.env.NODE_ENV != "production" && held.slice(0, i + 1).includes(true)) warn(`a compound variant on ${Object.keys(when).join(", ")} holds where no breakpoint or container condition expresses; it is left out there.`);
|
|
879
|
+
};
|
|
880
|
+
};
|
|
881
|
+
//#endregion
|
|
882
|
+
//#region src/core/recipe.ts
|
|
883
|
+
/**
|
|
884
|
+
* Recipes (PLAN §4.4, §5.7): a `recipe()` definition compiled to the table of
|
|
885
|
+
* compiled styles that the runtime resolver (`src/runtime/recipe.ts`) reads,
|
|
886
|
+
* and calls with literal props resolved at build time.
|
|
887
|
+
*
|
|
888
|
+
* The compiler works on class lists (core/atoms.ts); every cell of a table
|
|
889
|
+
* is one canonical class list as a compiled style (merge id → class), so the
|
|
890
|
+
* resolver only merges styles. Every variant value and every
|
|
891
|
+
* compound is compiled, used or not: nothing is tracked by tag or prop name,
|
|
892
|
+
* so renaming a component or a prop never loses CSS (PLAN §5.7).
|
|
893
|
+
*/
|
|
894
|
+
/** What a `recipe()` call is inside another static expression (an import, `extends`). */
|
|
895
|
+
var RecipeValue = class {
|
|
896
|
+
__vxSpecial = "recipe";
|
|
897
|
+
recipe;
|
|
898
|
+
constructor(recipe) {
|
|
899
|
+
this.recipe = recipe;
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
/** Each compiled recipe's key: what `recipeKey` writes for a parent in `extends`. */
|
|
903
|
+
const recipeKeys = /* @__PURE__ */ new WeakMap();
|
|
904
|
+
/**
|
|
905
|
+
* What a recipe compiles from, as a string: two evaluated definitions with
|
|
906
|
+
* one key compile alike in one system, whichever module or compile they
|
|
907
|
+
* come from (`ModuleCache.recipes`). Undefined when the definition holds a
|
|
908
|
+
* value the key cannot tell apart (a compiled style, a marker, a runtime
|
|
909
|
+
* value), which then compiles, or fails, every time.
|
|
910
|
+
*/
|
|
911
|
+
function recipeKey(definition) {
|
|
912
|
+
let ok = true;
|
|
913
|
+
const text = JSON.stringify(definition, (_, value) => {
|
|
914
|
+
if (value instanceof RecipeValue) {
|
|
915
|
+
const key = recipeKeys.get(value.recipe);
|
|
916
|
+
if (key === void 0) ok = false;
|
|
917
|
+
return { "\0recipe": key };
|
|
918
|
+
}
|
|
919
|
+
if (value !== null && typeof value === "object" && "__vxSpecial" in value) ok = false;
|
|
920
|
+
return value;
|
|
921
|
+
});
|
|
922
|
+
return ok ? text : void 0;
|
|
923
|
+
}
|
|
924
|
+
const KEYS = [
|
|
925
|
+
"base",
|
|
926
|
+
"slots",
|
|
927
|
+
"variants",
|
|
928
|
+
"compoundVariants",
|
|
929
|
+
"compoundSlots",
|
|
930
|
+
"defaultVariants",
|
|
931
|
+
"responsive",
|
|
932
|
+
"flatten",
|
|
933
|
+
"extends"
|
|
934
|
+
];
|
|
935
|
+
const HINT = "Write recipe({ base: { … }, variants: { size: { sm: { … }, md: { … } } } }).";
|
|
936
|
+
/** No-slot parts live under this key. */
|
|
937
|
+
const ROOT = "";
|
|
938
|
+
/** Compiles an evaluated definition. `extends` holds the parents' RecipeValues. */
|
|
939
|
+
function buildRecipe(definition, ctx) {
|
|
940
|
+
const fail = (path, message, hint) => ctx.error(path, "BAD_RECIPE", path ? `${path}: ${message}` : message, hint);
|
|
941
|
+
if (!isPlainObject(definition)) throw fail("", "recipe() takes one definition object", HINT);
|
|
942
|
+
for (const key of Object.keys(definition)) {
|
|
943
|
+
if (KEYS.includes(key)) continue;
|
|
944
|
+
const hint = key === "class" || key === "className" ? "Recipes take style objects: write base, or style in a compound." : key === "extend" ? "Write `extends`." : didYouMean(closestNames(key, KEYS));
|
|
945
|
+
throw fail(key, `\`${key}\` is not a recipe key`, hint);
|
|
946
|
+
}
|
|
947
|
+
const parents = parentsOf(definition.extends, fail);
|
|
948
|
+
const atoms = /* @__PURE__ */ new Map();
|
|
949
|
+
for (const parent of parents) for (const [cls, atom] of parent.atoms) atoms.set(cls, atom);
|
|
950
|
+
const style = (value, path) => {
|
|
951
|
+
if (value?.__vxSpecial === "style") throw ctx.error(path, "BAD_STYLE", `${path}: a compiled style is used inside a recipe; recipes take style objects`, "Write the style object itself.");
|
|
952
|
+
if (!isPlainObject(value)) throw fail(path, "expected a style object");
|
|
953
|
+
const out = ctx.normalize(value, path);
|
|
954
|
+
for (const atom of out) atoms.set(atom.className, atom);
|
|
955
|
+
return out.map((atom) => atom.className).join(" ");
|
|
956
|
+
};
|
|
957
|
+
const slotted = parents.filter((parent) => parent.slots !== void 0);
|
|
958
|
+
if (slotted.length > 0 && slotted.length < parents.length) throw fail("extends", "a recipe cannot extend recipes with slots and recipes without");
|
|
959
|
+
if (definition.slots !== void 0 && parents.length > 0 && slotted.length === 0) throw fail("slots", "a recipe with slots cannot extend recipes without slots");
|
|
960
|
+
const ownSlots = /* @__PURE__ */ new Map();
|
|
961
|
+
if (definition.slots !== void 0) {
|
|
962
|
+
if (!isPlainObject(definition.slots)) throw fail("slots", "expected an object of slot styles");
|
|
963
|
+
for (const [name, value] of Object.entries(definition.slots)) {
|
|
964
|
+
if (value === void 0) continue;
|
|
965
|
+
if (name === "") throw fail("slots", "a slot needs a name");
|
|
966
|
+
ownSlots.set(name, style(value, `slots.${name}`));
|
|
967
|
+
}
|
|
968
|
+
if (ownSlots.size === 0 && slotted.length === 0) throw fail("slots", "a recipe with slots names at least one");
|
|
969
|
+
}
|
|
970
|
+
const slots = definition.slots !== void 0 || slotted.length > 0 ? [.../* @__PURE__ */ new Set([...slotted.flatMap((parent) => parent.slots), ...ownSlots.keys()])] : void 0;
|
|
971
|
+
const keys = slots ?? [ROOT];
|
|
972
|
+
if (slots && definition.base !== void 0) throw fail("base", "a recipe with slots has no `base`", "Base styles live in the slots: slots: { root: { … }, label: { … } }.");
|
|
973
|
+
const base = slots ? Object.fromEntries(slots.map((slot) => [slot, ownSlots.get(slot) ?? ""])) : { [ROOT]: definition.base === void 0 ? "" : style(definition.base, "base") };
|
|
974
|
+
/** A value's or compound's style: a style, or a style per slot. */
|
|
975
|
+
const parts = (value, path) => {
|
|
976
|
+
if (!slots) return { [ROOT]: style(value, path) };
|
|
977
|
+
if (!isPlainObject(value)) throw fail(path, "expected an object of styles per slot");
|
|
978
|
+
const out = {};
|
|
979
|
+
for (const [slot, item] of Object.entries(value)) {
|
|
980
|
+
if (item === void 0) continue;
|
|
981
|
+
if (!slots.includes(slot)) throw fail(`${path}.${slot}`, `\`${slot}\` is not a slot of this recipe`, didYouMean(closestNames(slot, slots)) ?? `In a recipe with slots, styles are given per slot (${slots.join(", ")}).`);
|
|
982
|
+
out[slot] = style(item, `${path}.${slot}`);
|
|
983
|
+
}
|
|
984
|
+
return out;
|
|
985
|
+
};
|
|
986
|
+
const own = /* @__PURE__ */ new Map();
|
|
987
|
+
if (definition.variants !== void 0) {
|
|
988
|
+
if (!isPlainObject(definition.variants)) throw fail("variants", "expected an object of variants", HINT);
|
|
989
|
+
for (const [name, values] of Object.entries(definition.variants)) {
|
|
990
|
+
if (values === void 0) continue;
|
|
991
|
+
const path = `variants.${name}`;
|
|
992
|
+
if (!isPlainObject(values)) throw fail(path, "expected an object of values", HINT);
|
|
993
|
+
const table = /* @__PURE__ */ new Map();
|
|
994
|
+
for (const [value, item] of Object.entries(values)) if (item !== void 0) table.set(value, parts(item, `${path}.${value}`));
|
|
995
|
+
if (table.size === 0) throw fail(path, "a variant needs at least one value");
|
|
996
|
+
own.set(name, table);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
const merge = (list) => {
|
|
1000
|
+
const out = {};
|
|
1001
|
+
for (const slot of keys) {
|
|
1002
|
+
const merged = mergeClassNames(list.map((p) => p?.[slot] ?? "")).join(" ");
|
|
1003
|
+
if (merged || list.every((p) => p === void 0 || slot in p)) out[slot] = merged;
|
|
1004
|
+
}
|
|
1005
|
+
return out;
|
|
1006
|
+
};
|
|
1007
|
+
const variants = /* @__PURE__ */ new Map();
|
|
1008
|
+
for (const layer of [...parents.map((parent) => parent.variants), own]) for (const [name, values] of layer) {
|
|
1009
|
+
const into = variants.get(name) ?? /* @__PURE__ */ new Map();
|
|
1010
|
+
variants.set(name, into);
|
|
1011
|
+
for (const [value, item] of values) into.set(value, merge([into.get(value), item]));
|
|
1012
|
+
}
|
|
1013
|
+
for (const values of variants.values()) if (values.has("true") && !values.has("false")) values.set("false", {});
|
|
1014
|
+
else if (values.has("false") && !values.has("true")) values.set("true", {});
|
|
1015
|
+
const compoundVariants = parents.flatMap((parent) => parent.compoundVariants);
|
|
1016
|
+
if (definition.compoundVariants !== void 0) for (const [i, entry] of list(definition.compoundVariants, "compoundVariants", fail)) {
|
|
1017
|
+
const path = `compoundVariants.${i}`;
|
|
1018
|
+
const when = conditionsOf(entry, ["style"], variants, path, fail);
|
|
1019
|
+
compoundVariants.push({
|
|
1020
|
+
when,
|
|
1021
|
+
parts: parts(styleOf(entry, path, fail), `${path}.style`)
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
const compoundSlots = parents.flatMap((parent) => parent.compoundSlots);
|
|
1025
|
+
if (definition.compoundSlots !== void 0) {
|
|
1026
|
+
if (!slots) throw fail("compoundSlots", "only a recipe with slots has compound slots", "Use compoundVariants.");
|
|
1027
|
+
for (const [i, entry] of list(definition.compoundSlots, "compoundSlots", fail)) {
|
|
1028
|
+
const path = `compoundSlots.${i}`;
|
|
1029
|
+
const names = entry.slots;
|
|
1030
|
+
if (!Array.isArray(names) || names.length === 0) throw fail(`${path}.slots`, "expected an array of slot names", "Write slots: [\"a\", \"b\"].");
|
|
1031
|
+
for (const name of names) if (typeof name !== "string" || !slots.includes(name)) throw fail(`${path}.slots`, `\`${String(name)}\` is not a slot of this recipe`, didYouMean(closestNames(String(name), slots)));
|
|
1032
|
+
const when = conditionsOf(entry, ["slots", "style"], variants, path, fail);
|
|
1033
|
+
const classes = style(styleOf(entry, path, fail), `${path}.style`);
|
|
1034
|
+
const out = {};
|
|
1035
|
+
for (const name of names) out[name] = classes;
|
|
1036
|
+
compoundSlots.push({
|
|
1037
|
+
when,
|
|
1038
|
+
parts: out
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
const defaults = Object.assign({}, ...parents.map((parent) => parent.defaults));
|
|
1043
|
+
if (definition.defaultVariants !== void 0) {
|
|
1044
|
+
if (!isPlainObject(definition.defaultVariants)) throw fail("defaultVariants", "expected an object of variant values");
|
|
1045
|
+
for (const [name, value] of Object.entries(definition.defaultVariants)) {
|
|
1046
|
+
if (value === void 0) continue;
|
|
1047
|
+
defaults[name] = valueIn(variants, name, value, `defaultVariants.${name}`, fail, "default");
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
const responsive = new Set(parents.flatMap((parent) => [...parent.responsive]));
|
|
1051
|
+
const flag = definition.responsive;
|
|
1052
|
+
if (flag === true) for (const name of variants.keys()) responsive.add(name);
|
|
1053
|
+
else if (Array.isArray(flag)) for (const name of flag) {
|
|
1054
|
+
if (typeof name !== "string" || !variants.has(name)) throw fail("responsive", `\`${String(name)}\` is not a variant of this recipe`, didYouMean(closestNames(String(name), variants.keys())));
|
|
1055
|
+
responsive.add(name);
|
|
1056
|
+
}
|
|
1057
|
+
else if (flag !== void 0 && flag !== false) throw fail("responsive", "expected an array of variant names, or true for all of them");
|
|
1058
|
+
const axes = responsiveConditions(ctx.system).byName;
|
|
1059
|
+
for (const name of responsive) for (const [value, item] of variants.get(name)) for (const cls of Object.values(item).flatMap((classes) => classes.split(" "))) {
|
|
1060
|
+
const condition = atoms.get(cls)?.chain.find((c) => axes.has(c));
|
|
1061
|
+
if (condition === void 0) continue;
|
|
1062
|
+
const declared = own.get(name)?.has(value);
|
|
1063
|
+
throw fail(declared ? `variants.${name}.${value}` : "responsive", `\`${name}\` is responsive, so its \`${value}\` value cannot hold \`_${condition}\` styles of its own`, `Its values hold under the breakpoints a call gives them: write \`${name}: { base: …, _${condition}: … }\` in the call, or move the \`_${condition}\` styles to a variant that is not responsive.`);
|
|
1064
|
+
}
|
|
1065
|
+
const compiled = compile({
|
|
1066
|
+
slots,
|
|
1067
|
+
base: parents.length > 0 ? merge([...parents.map((parent) => parent.base), base]) : base,
|
|
1068
|
+
variants,
|
|
1069
|
+
compoundVariants,
|
|
1070
|
+
compoundSlots,
|
|
1071
|
+
defaults,
|
|
1072
|
+
responsive,
|
|
1073
|
+
flatten: flattened(definition.flatten, parents, slots, fail),
|
|
1074
|
+
atoms
|
|
1075
|
+
}, ctx.system);
|
|
1076
|
+
const key = recipeKey(definition);
|
|
1077
|
+
if (key !== void 0) recipeKeys.set(compiled, key);
|
|
1078
|
+
return compiled;
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* What `flatten` says, or the parents' choice when it says nothing: `true`
|
|
1082
|
+
* flattens the base (every slot of a recipe with slots), a list of slot
|
|
1083
|
+
* names those slots' bases, `false` none.
|
|
1084
|
+
*/
|
|
1085
|
+
function flattened(value, parents, slots, fail) {
|
|
1086
|
+
if (value === void 0) return new Set(parents.flatMap((parent) => [...parent.flatten]));
|
|
1087
|
+
if (value === true) return new Set(slots ?? [ROOT]);
|
|
1088
|
+
if (value === false) return /* @__PURE__ */ new Set();
|
|
1089
|
+
if (!slots || !Array.isArray(value)) throw fail("flatten", slots ? "expected true, false or an array of slot names" : "expected true or false: a recipe without slots flattens its base or not", slots ? "Write flatten: true, or flatten: [\"root\", \"cell\"]." : "Write flatten: true.");
|
|
1090
|
+
const out = /* @__PURE__ */ new Set();
|
|
1091
|
+
for (const name of value) {
|
|
1092
|
+
if (typeof name !== "string" || !slots.includes(name)) throw fail("flatten", `\`${String(name)}\` is not a slot of this recipe`, didYouMean(closestNames(String(name), slots)) ?? `Its slots: ${slots.join(", ")}.`);
|
|
1093
|
+
out.add(name);
|
|
1094
|
+
}
|
|
1095
|
+
return out;
|
|
1096
|
+
}
|
|
1097
|
+
function parentsOf(value, fail) {
|
|
1098
|
+
if (value === void 0) return [];
|
|
1099
|
+
return (Array.isArray(value) ? value : [value]).map((item) => {
|
|
1100
|
+
if (item instanceof RecipeValue) return item.recipe.data;
|
|
1101
|
+
throw fail("extends", "`extends` takes recipes", "Write extends: button, or extends: [button, focusRing], with recipes made by recipe().");
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
function list(value, path, fail) {
|
|
1105
|
+
if (!Array.isArray(value)) throw fail(path, "expected an array");
|
|
1106
|
+
return value.map((entry, i) => {
|
|
1107
|
+
if (!isPlainObject(entry)) throw fail(`${path}.${i}`, "expected an object");
|
|
1108
|
+
return [i, entry];
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
function styleOf(entry, path, fail) {
|
|
1112
|
+
if (entry.style !== void 0) return entry.style;
|
|
1113
|
+
throw fail(path, "a compound needs a `style`", "class" in entry || "className" in entry ? "Recipes take style objects: write style: { … }." : "Write { size: \"sm\", style: { … } }.");
|
|
1114
|
+
}
|
|
1115
|
+
/** A compound's variant conditions: variant → the values it holds for. */
|
|
1116
|
+
function conditionsOf(entry, reserved, variants, path, fail) {
|
|
1117
|
+
const when = {};
|
|
1118
|
+
for (const [name, value] of Object.entries(entry)) {
|
|
1119
|
+
if (reserved.includes(name) || value === void 0) continue;
|
|
1120
|
+
if (name === "class" || name === "className") throw fail(`${path}.${name}`, `\`${name}\` is not a variant`, "Write style: { … }.");
|
|
1121
|
+
const items = Array.isArray(value) ? value : [value];
|
|
1122
|
+
if (items.length === 0) throw fail(`${path}.${name}`, "lists no value, so it never holds");
|
|
1123
|
+
when[name] = [...new Set(items.map((item) => valueIn(variants, name, item, `${path}.${name}`, fail, "compound")))];
|
|
1124
|
+
}
|
|
1125
|
+
return when;
|
|
1126
|
+
}
|
|
1127
|
+
/** A variant value as the table keys it, checked against the variant's values. */
|
|
1128
|
+
function valueIn(variants, name, value, path, fail, kind) {
|
|
1129
|
+
const values = variants.get(name);
|
|
1130
|
+
if (!values) throw fail(path, `\`${name}\` is not a variant of this recipe`, didYouMean(closestNames(name, variants.keys())));
|
|
1131
|
+
if (isPlainObject(value)) throw kind === "default" ? fail(path, "a default is one value, not a value per condition", `Give the value per condition in the call: recipe({ ${name}: { base: …, _md: … } }).`) : fail(path, "a compound names values, not a value per condition", `List the values it holds for (${name}: ["…", "…"]); with responsive props it is evaluated per breakpoint.`);
|
|
1132
|
+
const key = valueKey(value);
|
|
1133
|
+
if (key === void 0 || !values.has(key)) throw fail(path, `${describeValue(value)} is not a value of \`${name}\``, didYouMean(closestNames(String(key), values.keys())) ?? `Its values: ${[...values.keys()].join(", ")}.`);
|
|
1134
|
+
return key;
|
|
1135
|
+
}
|
|
1136
|
+
/** Booleans and numbers name values by their string: `true` is the `"true"` value. */
|
|
1137
|
+
function valueKey(value) {
|
|
1138
|
+
if (typeof value === "string") return value;
|
|
1139
|
+
if (typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
1140
|
+
}
|
|
1141
|
+
function describeValue(value) {
|
|
1142
|
+
return typeof value === "string" ? `\`"${value}"\`` : `\`${String(value)}\``;
|
|
1143
|
+
}
|
|
1144
|
+
const responsiveCache = /* @__PURE__ */ new WeakMap();
|
|
1145
|
+
/** The breakpoint and container conditions of a system, with where each holds. */
|
|
1146
|
+
function responsiveConditions(system) {
|
|
1147
|
+
const cached = responsiveCache.get(system);
|
|
1148
|
+
if (cached) return cached;
|
|
1149
|
+
const cap = (name) => name.charAt(0).toUpperCase() + name.slice(1);
|
|
1150
|
+
const rank = (name) => system.conditions.get(name)?.rank;
|
|
1151
|
+
const ordered = (names) => [...names].filter((n) => rank(n) !== void 0).sort((a, b) => rank(a) - rank(b));
|
|
1152
|
+
const bps = ordered(system.tokens.get("breakpoints")?.keys() ?? []);
|
|
1153
|
+
const containers = ordered([...system.tokens.get("containers")?.keys() ?? []].map((n) => `container${cap(n)}`));
|
|
1154
|
+
const list = [];
|
|
1155
|
+
const add = (name, axis, lo, hi, key) => {
|
|
1156
|
+
const r = rank(name);
|
|
1157
|
+
if (r !== void 0) list.push({
|
|
1158
|
+
name,
|
|
1159
|
+
axis,
|
|
1160
|
+
lo,
|
|
1161
|
+
hi,
|
|
1162
|
+
rank: r,
|
|
1163
|
+
key
|
|
1164
|
+
});
|
|
1165
|
+
};
|
|
1166
|
+
const end = bps.length + 1;
|
|
1167
|
+
bps.forEach((bp, i) => {
|
|
1168
|
+
add(`max${cap(bp)}`, "breakpoints", 0, i + 1, `,${bp}`);
|
|
1169
|
+
add(bp, "breakpoints", i + 1, end, `${bp},`);
|
|
1170
|
+
for (const [j, hi] of bps.slice(i + 1).entries()) add(`${bp}To${cap(hi)}`, "breakpoints", i + 1, i + j + 2, `${bp},${hi}`);
|
|
1171
|
+
});
|
|
1172
|
+
containers.forEach((name, i) => add(name, "containers", i + 1, containers.length + 1, `${name},`));
|
|
1173
|
+
list.sort((a, b) => a.rank - b.rank);
|
|
1174
|
+
const props = list.filter((c) => c.axis === "containers" || c.hi === end);
|
|
1175
|
+
const out = {
|
|
1176
|
+
list,
|
|
1177
|
+
byName: new Map(list.map((c) => [c.name, c])),
|
|
1178
|
+
props,
|
|
1179
|
+
breakpoints: props.filter((c) => c.axis === "breakpoints").length
|
|
1180
|
+
};
|
|
1181
|
+
responsiveCache.set(system, out);
|
|
1182
|
+
return out;
|
|
1183
|
+
}
|
|
1184
|
+
/** Whether some width satisfies every breakpoint condition of `chain` (containers always can). */
|
|
1185
|
+
function satisfiable(chain, responsive) {
|
|
1186
|
+
let lo = 0;
|
|
1187
|
+
let hi = Infinity;
|
|
1188
|
+
for (const name of chain) {
|
|
1189
|
+
const interval = responsive.byName.get(name);
|
|
1190
|
+
if (interval?.axis !== "breakpoints") continue;
|
|
1191
|
+
lo = Math.max(lo, interval.lo);
|
|
1192
|
+
hi = Math.min(hi, interval.hi);
|
|
1193
|
+
}
|
|
1194
|
+
return lo < hi;
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* Whether `chain` holds only where `condition` does: it has the condition,
|
|
1198
|
+
* or a breakpoint or container condition inside it (`md` inside `sm`).
|
|
1199
|
+
*/
|
|
1200
|
+
function implies(chain, condition, responsive) {
|
|
1201
|
+
if (chain.includes(condition)) return true;
|
|
1202
|
+
const outer = responsive.byName.get(condition);
|
|
1203
|
+
return outer !== void 0 && chain.some((name) => {
|
|
1204
|
+
const inner = responsive.byName.get(name);
|
|
1205
|
+
return inner?.axis === outer.axis && inner.lo >= outer.lo && inner.hi <= outer.hi;
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
function compile(data, system) {
|
|
1209
|
+
const { slots } = data;
|
|
1210
|
+
const responsive = responsiveConditions(system);
|
|
1211
|
+
const atoms = new Map(data.atoms);
|
|
1212
|
+
const rank = (name) => system.conditions.get(name).rank;
|
|
1213
|
+
const words = (classes) => classes ? classes.split(" ") : [];
|
|
1214
|
+
const longhandCache = /* @__PURE__ */ new Map();
|
|
1215
|
+
const longhands = (cls) => {
|
|
1216
|
+
let out = longhandCache.get(cls);
|
|
1217
|
+
if (!out) {
|
|
1218
|
+
const { property } = atoms.get(cls);
|
|
1219
|
+
out = [.../* @__PURE__ */ new Set([...longhandsOf(property, "ltr"), ...longhandsOf(property, "rtl")])];
|
|
1220
|
+
longhandCache.set(cls, out);
|
|
1221
|
+
}
|
|
1222
|
+
return out;
|
|
1223
|
+
};
|
|
1224
|
+
const longhandsOfParts = (parts) => new Map(Object.entries(parts).map(([slot, cls]) => [slot, new Set(words(cls).flatMap(longhands))]));
|
|
1225
|
+
const overlap = (a, b) => [...a].some(([slot, set]) => [...set].some((longhand) => b.get(slot)?.has(longhand)));
|
|
1226
|
+
const rechained = /* @__PURE__ */ new Map();
|
|
1227
|
+
/** `classes` under every one of `conditions`, without the classes that can never hold. */
|
|
1228
|
+
const under = (classes, conditions) => {
|
|
1229
|
+
const id = `${conditions.join(" ")}|${classes}`;
|
|
1230
|
+
let out = rechained.get(id);
|
|
1231
|
+
if (out === void 0) {
|
|
1232
|
+
out = rechain(classes, conditions, atoms, system, responsive, longhands);
|
|
1233
|
+
rechained.set(id, out);
|
|
1234
|
+
}
|
|
1235
|
+
return out;
|
|
1236
|
+
};
|
|
1237
|
+
/**
|
|
1238
|
+
* `parts` under `condition` (then `extra`), with the classes of `before`
|
|
1239
|
+
* (the base, and the variant values a compound requires) on the same
|
|
1240
|
+
* longhands whose chains rank below the conditions or end at one: in the
|
|
1241
|
+
* cascade those win over the parts' base-chain classes (a base `_hover`
|
|
1242
|
+
* colour over a value's colour, a base `_md` padding over a value's
|
|
1243
|
+
* padding at md), and rechained alongside them they still do. `before`
|
|
1244
|
+
* merges first, in order, as the resolver merges it.
|
|
1245
|
+
*/
|
|
1246
|
+
const cover = (parts, condition, extra = [], before = [data.base]) => {
|
|
1247
|
+
const below = Math.max(...[condition, ...extra].map(rank));
|
|
1248
|
+
const out = {};
|
|
1249
|
+
for (const [slot, classes] of Object.entries(parts)) {
|
|
1250
|
+
if (!classes) continue;
|
|
1251
|
+
const set = new Set(words(classes).flatMap(longhands));
|
|
1252
|
+
const low = before.map((other) => words(other[slot]).filter((cls) => {
|
|
1253
|
+
const { chain } = atoms.get(cls);
|
|
1254
|
+
return chain.length > 0 && chain.every((name) => rank(name) <= below) && longhands(cls).some((longhand) => set.has(longhand));
|
|
1255
|
+
}).join(" "));
|
|
1256
|
+
const merged = low.some(Boolean) ? mergeClassNames([...low, classes]).join(" ") : classes;
|
|
1257
|
+
const result = under(merged, [condition, ...extra]);
|
|
1258
|
+
if (result) out[slot] = result;
|
|
1259
|
+
}
|
|
1260
|
+
return out;
|
|
1261
|
+
};
|
|
1262
|
+
const v = {};
|
|
1263
|
+
for (const [name, values] of data.variants) v[name] = Object.fromEntries([...values].map(([value, parts]) => [value, classesOf(parts, slots, false)]));
|
|
1264
|
+
const d = {};
|
|
1265
|
+
for (const name of data.variants.keys()) {
|
|
1266
|
+
const value = data.defaults[name];
|
|
1267
|
+
if (value !== void 0) d[name] = value;
|
|
1268
|
+
else if (data.variants.get(name).has("false")) d[name] = "false";
|
|
1269
|
+
}
|
|
1270
|
+
const responsiveVariants = [...data.variants.keys()].filter((name) => data.responsive.has(name));
|
|
1271
|
+
const conditional = responsiveVariants.length > 0 && responsive.props.length > 0;
|
|
1272
|
+
let r;
|
|
1273
|
+
if (responsiveVariants.length > 0) {
|
|
1274
|
+
r = {};
|
|
1275
|
+
for (const name of responsiveVariants) {
|
|
1276
|
+
const values = data.variants.get(name);
|
|
1277
|
+
const out = {};
|
|
1278
|
+
r[name] = out;
|
|
1279
|
+
if (!conditional) continue;
|
|
1280
|
+
const chains = /* @__PURE__ */ new Map();
|
|
1281
|
+
for (const parts of values.values()) for (const cls of Object.values(parts).flatMap(words)) {
|
|
1282
|
+
const { chain } = atoms.get(cls);
|
|
1283
|
+
chains.set(chain.join(" "), chain);
|
|
1284
|
+
}
|
|
1285
|
+
for (const { name: condition } of responsive.props) {
|
|
1286
|
+
const above = /* @__PURE__ */ new Map();
|
|
1287
|
+
for (const chain of chains.values()) {
|
|
1288
|
+
const high = chain.filter((c) => rank(c) > rank(condition));
|
|
1289
|
+
if (high.length > 0) above.set(high.join(" "), high);
|
|
1290
|
+
}
|
|
1291
|
+
for (const [value, parts] of values) {
|
|
1292
|
+
const cells = [cover(parts, condition), ...[...above.values()].map((high) => cover(parts, condition, high))];
|
|
1293
|
+
const merged = {};
|
|
1294
|
+
for (const slot of slots ?? [ROOT]) {
|
|
1295
|
+
const classes = cells.length === 1 ? cells[0][slot] ?? "" : mergeClassNames(cells.map((cell) => cell[slot] ?? "")).join(" ");
|
|
1296
|
+
if (classes) merged[slot] = classes;
|
|
1297
|
+
}
|
|
1298
|
+
if (isEmpty(merged)) continue;
|
|
1299
|
+
(out[value] ??= {})[condition] = classesOf(merged, slots, false);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
const compounds = [...data.compoundVariants, ...data.compoundSlots];
|
|
1305
|
+
const cuts = conditional ? compoundCuts() : compounds.map(() => []);
|
|
1306
|
+
const c = compounds.map((compound, i) => {
|
|
1307
|
+
const classes = classesOf(compound.parts, slots, false);
|
|
1308
|
+
const cut = cuts[i];
|
|
1309
|
+
if (cut.length === 0) return [compound.when, classes];
|
|
1310
|
+
const required = [...data.variants].filter(([name]) => compound.when[name]?.length === 1).map(([name, values]) => values.get(compound.when[name][0]));
|
|
1311
|
+
const ranges = {};
|
|
1312
|
+
for (const interval of responsive.list) {
|
|
1313
|
+
const parts = cover(compound.parts, interval.name, [], [data.base, ...required]);
|
|
1314
|
+
if (!isEmpty(parts)) ranges[interval.key] = classesOf(parts, slots, false);
|
|
1315
|
+
}
|
|
1316
|
+
return isEmpty(ranges) ? [compound.when, classes] : [
|
|
1317
|
+
compound.when,
|
|
1318
|
+
classes,
|
|
1319
|
+
ranges,
|
|
1320
|
+
cut
|
|
1321
|
+
];
|
|
1322
|
+
});
|
|
1323
|
+
/**
|
|
1324
|
+
* The responsive variants whose conditions cut each compound's axis: those
|
|
1325
|
+
* it names, those whose values style its longhands (their cells would
|
|
1326
|
+
* outrank it otherwise), and those of every compound it shares longhands
|
|
1327
|
+
* with, so compounds that compete are cut alike and their order decides.
|
|
1328
|
+
*/
|
|
1329
|
+
function compoundCuts() {
|
|
1330
|
+
const variantLonghands = /* @__PURE__ */ new Map();
|
|
1331
|
+
for (const name of responsiveVariants) {
|
|
1332
|
+
const into = /* @__PURE__ */ new Map();
|
|
1333
|
+
for (const parts of data.variants.get(name).values()) for (const [slot, set] of longhandsOfParts(parts)) {
|
|
1334
|
+
const all = into.get(slot) ?? /* @__PURE__ */ new Set();
|
|
1335
|
+
for (const longhand of set) all.add(longhand);
|
|
1336
|
+
into.set(slot, all);
|
|
1337
|
+
}
|
|
1338
|
+
variantLonghands.set(name, into);
|
|
1339
|
+
}
|
|
1340
|
+
const own = compounds.map((compound) => longhandsOfParts(compound.parts));
|
|
1341
|
+
const cut = compounds.map((compound, i) => new Set(responsiveVariants.filter((name) => name in compound.when || overlap(own[i], variantLonghands.get(name)))));
|
|
1342
|
+
const group = compounds.map((_, i) => i);
|
|
1343
|
+
const find = (i) => group[i] === i ? i : group[i] = find(group[i]);
|
|
1344
|
+
for (let i = 0; i < compounds.length; i++) for (let j = i + 1; j < compounds.length; j++) if (overlap(own[i], own[j])) group[find(j)] = find(i);
|
|
1345
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1346
|
+
for (const [i, set] of cut.entries()) {
|
|
1347
|
+
const into = merged.get(find(i)) ?? /* @__PURE__ */ new Set();
|
|
1348
|
+
for (const name of set) into.add(name);
|
|
1349
|
+
merged.set(find(i), into);
|
|
1350
|
+
}
|
|
1351
|
+
return compounds.map((_, i) => responsiveVariants.filter((name) => merged.get(find(i)).has(name)));
|
|
1352
|
+
}
|
|
1353
|
+
const rules = /* @__PURE__ */ new Map();
|
|
1354
|
+
const base = { ...data.base };
|
|
1355
|
+
for (const slot of data.flatten) {
|
|
1356
|
+
const flat = flattenBase(words(data.base[slot]).map((cls) => atoms.get(cls)), system);
|
|
1357
|
+
if (!flat) continue;
|
|
1358
|
+
rules.set(flat.className, flat.rule);
|
|
1359
|
+
base[slot] = mergeClassNames([[flat.className, ...flat.kept].join(" ")]).join(" ");
|
|
1360
|
+
}
|
|
1361
|
+
const table = {
|
|
1362
|
+
k: [...data.variants.keys()],
|
|
1363
|
+
...slots ? { s: slots } : {},
|
|
1364
|
+
b: classesOf(base, slots, true),
|
|
1365
|
+
v,
|
|
1366
|
+
c,
|
|
1367
|
+
d,
|
|
1368
|
+
...conditional ? { q: [responsive.props.map((c) => c.name), responsive.breakpoints] } : {},
|
|
1369
|
+
...r ? { r } : {}
|
|
1370
|
+
};
|
|
1371
|
+
const used = /* @__PURE__ */ new Set();
|
|
1372
|
+
for (const cls of tableClasses(table)) if (!rules.has(cls)) used.add(cls);
|
|
1373
|
+
return {
|
|
1374
|
+
data,
|
|
1375
|
+
table,
|
|
1376
|
+
atoms: [...used].sort().map((cls) => atoms.get(cls)),
|
|
1377
|
+
rules: [...rules.values()].sort((a, b) => compareStrings(a.id, b.id)),
|
|
1378
|
+
system
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
/**
|
|
1382
|
+
* `classes` (one canonical class string) under every one of `conditions`,
|
|
1383
|
+
* in the order the cascade gave them. Adding the same conditions to every
|
|
1384
|
+
* chain keeps their order, except where a chain already held, or implied,
|
|
1385
|
+
* one of them (`md` under `sm`): it keeps its chain, and the condition that
|
|
1386
|
+
* made it win over another class may now be in both. Where that turns a
|
|
1387
|
+
* winner into a loser, a loser the winner now holds wherever it does is
|
|
1388
|
+
* left out; otherwise the winner is also written under the loser's chain,
|
|
1389
|
+
* so wherever both hold it still wins. Classes whose chain no width
|
|
1390
|
+
* satisfies (`max-md:lg:`) are left out.
|
|
1391
|
+
*/
|
|
1392
|
+
function rechain(classes, conditions, atoms, system, responsive, longhands) {
|
|
1393
|
+
if (classes === "") return "";
|
|
1394
|
+
const ranked = (a, b, chain) => compareChains(chain(a), chain(b), system) || a.atom.tier - b.atom.tier;
|
|
1395
|
+
const entries = [];
|
|
1396
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1397
|
+
const add = (cls, atom, chain) => {
|
|
1398
|
+
const id = `${cls}|${chain.join(" ")}`;
|
|
1399
|
+
if (seen.has(id) || !satisfiable(chain, responsive)) return false;
|
|
1400
|
+
seen.add(id);
|
|
1401
|
+
entries.push({
|
|
1402
|
+
cls,
|
|
1403
|
+
atom,
|
|
1404
|
+
chain
|
|
1405
|
+
});
|
|
1406
|
+
return true;
|
|
1407
|
+
};
|
|
1408
|
+
let kept = false;
|
|
1409
|
+
for (const cls of classes.split(" ")) {
|
|
1410
|
+
const atom = atoms.get(cls);
|
|
1411
|
+
if (!atom) throw new Error(`vicss: the recipe class ${cls} has no atom`);
|
|
1412
|
+
const added = conditions.filter((condition) => !implies(atom.chain, condition, responsive));
|
|
1413
|
+
kept ||= added.length < conditions.length;
|
|
1414
|
+
add(cls, atom, added.length > 0 ? canonicalChain([...atom.chain, ...added], system) : atom.chain);
|
|
1415
|
+
}
|
|
1416
|
+
const dead = /* @__PURE__ */ new Set();
|
|
1417
|
+
for (let round = 0, changed = kept; changed && round < 8; round++) {
|
|
1418
|
+
changed = false;
|
|
1419
|
+
for (const winner of entries) for (const loser of entries) {
|
|
1420
|
+
if (dead.has(winner) || dead.has(loser) || winner.atom === loser.atom) continue;
|
|
1421
|
+
if (ranked(winner, loser, (e) => e.atom.chain) <= 0) continue;
|
|
1422
|
+
if (ranked(winner, loser, (e) => e.chain) > 0) continue;
|
|
1423
|
+
const theirs = longhands(loser.cls);
|
|
1424
|
+
const mine = longhands(winner.cls);
|
|
1425
|
+
if (!mine.some((longhand) => theirs.includes(longhand))) continue;
|
|
1426
|
+
if (winner.chain.every((name) => loser.chain.includes(name)) && theirs.every((longhand) => mine.includes(longhand))) {
|
|
1427
|
+
dead.add(loser);
|
|
1428
|
+
changed = true;
|
|
1429
|
+
continue;
|
|
1430
|
+
}
|
|
1431
|
+
const both = [.../* @__PURE__ */ new Set([...winner.chain, ...loser.chain])];
|
|
1432
|
+
const chain = canonicalChain(both.filter((name) => !both.some((other) => other !== name && implies([other], name, responsive))), system);
|
|
1433
|
+
if (chain.join(" ") !== winner.chain.join(" ") && add(winner.cls, winner.atom, chain)) changed = true;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1437
|
+
for (const entry of entries) {
|
|
1438
|
+
if (dead.has(entry)) continue;
|
|
1439
|
+
const { cls, atom, chain } = entry;
|
|
1440
|
+
const labels = chain.map((name) => system.conditions.get(name).label);
|
|
1441
|
+
const className = makeClassName(labels, atom.key, parseClassName(cls).value);
|
|
1442
|
+
if (!atoms.has(className)) atoms.set(className, {
|
|
1443
|
+
...atom,
|
|
1444
|
+
className,
|
|
1445
|
+
chain
|
|
1446
|
+
});
|
|
1447
|
+
const id = atom.chain.join(" ");
|
|
1448
|
+
const group = groups.get(id) ?? {
|
|
1449
|
+
chain: atom.chain,
|
|
1450
|
+
names: []
|
|
1451
|
+
};
|
|
1452
|
+
group.names.push(className);
|
|
1453
|
+
groups.set(id, group);
|
|
1454
|
+
}
|
|
1455
|
+
const ordered = [...groups.values()].sort((a, b) => compareChains(a.chain, b.chain, system));
|
|
1456
|
+
if (ordered.length === 1) return ordered[0].names.sort().join(" ");
|
|
1457
|
+
return mergeClassNames(ordered.map((group) => group.names.join(" "))).join(" ");
|
|
1458
|
+
}
|
|
1459
|
+
/** Parts as the table writes them: a style, or one per slot (empty ones only when `full`). */
|
|
1460
|
+
function classesOf(parts, slots, full) {
|
|
1461
|
+
if (!slots) return toRecord(parts[ROOT] ?? "");
|
|
1462
|
+
const out = {};
|
|
1463
|
+
for (const slot of slots) {
|
|
1464
|
+
const classes = parts[slot] ?? "";
|
|
1465
|
+
if (full || classes !== "") out[slot] = toRecord(classes);
|
|
1466
|
+
}
|
|
1467
|
+
return out;
|
|
1468
|
+
}
|
|
1469
|
+
function isEmpty(record) {
|
|
1470
|
+
for (const _ in record) return false;
|
|
1471
|
+
return true;
|
|
1472
|
+
}
|
|
1473
|
+
/** Every class in a table. */
|
|
1474
|
+
function* tableClasses(table) {
|
|
1475
|
+
const slotted = table.s !== void 0;
|
|
1476
|
+
const of = function* (classes) {
|
|
1477
|
+
const styles = slotted ? Object.values(classes) : [classes];
|
|
1478
|
+
for (const style of styles) yield* Object.values(style);
|
|
1479
|
+
};
|
|
1480
|
+
yield* of(table.b);
|
|
1481
|
+
for (const values of Object.values(table.v)) for (const c of Object.values(values)) yield* of(c);
|
|
1482
|
+
for (const [, classes, ranges] of table.c) {
|
|
1483
|
+
yield* of(classes);
|
|
1484
|
+
for (const c of Object.values(ranges ?? {})) yield* of(c);
|
|
1485
|
+
}
|
|
1486
|
+
for (const values of Object.values(table.r ?? {})) for (const byCondition of Object.values(values)) for (const c of Object.values(byCondition)) yield* of(c);
|
|
1487
|
+
}
|
|
1488
|
+
/**
|
|
1489
|
+
* The table as a compact JS literal: identifier keys unquoted, no spaces,
|
|
1490
|
+
* each cell's classes as `emit` writes them.
|
|
1491
|
+
*/
|
|
1492
|
+
function printTable(table, emit) {
|
|
1493
|
+
const slotted = table.s !== void 0;
|
|
1494
|
+
const print = (value) => {
|
|
1495
|
+
if (typeof value === "string" || typeof value === "number") return JSON.stringify(value);
|
|
1496
|
+
if (Array.isArray(value)) return `[${value.map(print).join(",")}]`;
|
|
1497
|
+
return `{${Object.entries(value).map(([key, item]) => `${propertyKey(key)}:${print(item)}`).join(",")}}`;
|
|
1498
|
+
};
|
|
1499
|
+
const cell = (classes) => slotted ? `{${Object.entries(classes).map(([slot, style]) => `${propertyKey(slot)}:${styleLiteral(style, emit)}`).join(",")}}` : styleLiteral(classes, emit);
|
|
1500
|
+
const cells = (record, each) => `{${Object.entries(record).map(([key, item]) => `${propertyKey(key)}:${each(item)}`).join(",")}}`;
|
|
1501
|
+
const parts = [`k:${print(table.k)}`];
|
|
1502
|
+
if (table.s) parts.push(`s:${print(table.s)}`);
|
|
1503
|
+
parts.push(`b:${cell(table.b)}`);
|
|
1504
|
+
parts.push(`v:${cells(table.v, (values) => cells(values, cell))}`);
|
|
1505
|
+
parts.push(`c:[${table.c.map(([when, classes, ranges, cut]) => `[${[
|
|
1506
|
+
print(when),
|
|
1507
|
+
cell(classes),
|
|
1508
|
+
...ranges ? [cells(ranges, cell), print(cut ?? [])] : []
|
|
1509
|
+
].join(",")}]`).join(",")}]`);
|
|
1510
|
+
parts.push(`d:${print(table.d)}`);
|
|
1511
|
+
if (table.q) parts.push(`q:${print(table.q)}`);
|
|
1512
|
+
if (table.r) parts.push(`r:${cells(table.r, (values) => cells(values, (byCondition) => cells(byCondition, cell)))}`);
|
|
1513
|
+
if (table.f) parts.push(`f:${cells(table.f, (style) => styleLiteral(style, emit))}`);
|
|
1514
|
+
return `{${parts.join(",")}}`;
|
|
1515
|
+
}
|
|
1516
|
+
const resolvers = /* @__PURE__ */ new WeakMap();
|
|
1517
|
+
/**
|
|
1518
|
+
* The classes a call with these props resolves to. The runtime resolver
|
|
1519
|
+
* computes them, so a folded call and a runtime one agree class for class;
|
|
1520
|
+
* this checks the props first, since the call is literal: what the runtime
|
|
1521
|
+
* would ignore, warn about or resolve wrongly is a build error, with `fail`
|
|
1522
|
+
* given the path inside the props.
|
|
1523
|
+
*/
|
|
1524
|
+
function resolveRecipe(recipe, props, fail) {
|
|
1525
|
+
checkProps(recipe, props, fail);
|
|
1526
|
+
let resolve = resolvers.get(recipe);
|
|
1527
|
+
if (!resolve) {
|
|
1528
|
+
const { table } = recipe;
|
|
1529
|
+
resolve = __vxRecipe(table, table.q ? __vxResponsive : void 0);
|
|
1530
|
+
resolvers.set(recipe, resolve);
|
|
1531
|
+
}
|
|
1532
|
+
return resolve(props ?? {});
|
|
1533
|
+
}
|
|
1534
|
+
function checkProps(recipe, props, fail) {
|
|
1535
|
+
const { table, data, system } = recipe;
|
|
1536
|
+
const responsive = responsiveConditions(system);
|
|
1537
|
+
if (props !== void 0 && props !== null && !isPlainObject(props)) throw fail("", "a recipe takes an object of variant props");
|
|
1538
|
+
const given = props ?? {};
|
|
1539
|
+
for (const key of Object.keys(given)) if (!Object.hasOwn(table.v, key)) throw fail(key, `\`${key}\` is not a variant of this recipe`, didYouMean(closestNames(key, table.k)) ?? (table.k.length > 0 ? `Its variants: ${table.k.join(", ")}.` : "It has no variants."));
|
|
1540
|
+
const check = (name, raw, path) => {
|
|
1541
|
+
if (raw === void 0 || raw === null) return table.d[name];
|
|
1542
|
+
const key = valueKey(raw);
|
|
1543
|
+
const values = table.v[name];
|
|
1544
|
+
if (key === void 0 || !Object.hasOwn(values, key)) throw fail(path, `${describeValue(raw)} is not a value of \`${name}\``, didYouMean(closestNames(String(key), Object.keys(values))) ?? `Its values: ${Object.keys(values).join(", ")}.`);
|
|
1545
|
+
return key;
|
|
1546
|
+
};
|
|
1547
|
+
const [order, breakpoints] = table.q ?? [[], 0];
|
|
1548
|
+
/** Value without conditions per variant, and each responsive prop's values per condition. */
|
|
1549
|
+
const values = /* @__PURE__ */ new Map();
|
|
1550
|
+
const conditional = /* @__PURE__ */ new Map();
|
|
1551
|
+
for (const name of table.k) {
|
|
1552
|
+
const raw = given[name];
|
|
1553
|
+
if (!isPlainObject(raw)) {
|
|
1554
|
+
values.set(name, check(name, raw, name));
|
|
1555
|
+
continue;
|
|
1556
|
+
}
|
|
1557
|
+
if (!data.responsive.has(name)) throw fail(name, `\`${name}\` is not a responsive variant`, "List it in the recipe's `responsive` to give it a value per breakpoint.");
|
|
1558
|
+
values.set(name, check(name, raw.base, `${name}.base`));
|
|
1559
|
+
const set = /* @__PURE__ */ new Map();
|
|
1560
|
+
for (const [key, item] of Object.entries(raw)) {
|
|
1561
|
+
if (key === "base" || item === void 0 || item === null) continue;
|
|
1562
|
+
const condition = key.startsWith("_") ? key.slice(1) : void 0;
|
|
1563
|
+
if (condition === void 0 || !order.includes(condition)) {
|
|
1564
|
+
const range = condition === void 0 ? void 0 : responsive.byName.get(condition);
|
|
1565
|
+
throw fail(`${name}.${key}`, `\`${key}\` is not a breakpoint or container condition`, range ? "Responsive values are mobile-first: give the value from each breakpoint on (`{ base: …, _md: … }`), which covers below and between breakpoints too." : didYouMean(closestNames(key.replace(/^_/, ""), order).map((n) => `_${n}`)) ?? (order.length > 0 ? "A responsive value takes `base` and breakpoint or container conditions." : "The design system has no breakpoints or containers to give values under."));
|
|
1566
|
+
}
|
|
1567
|
+
set.set(condition, check(name, item, `${name}.${key}`));
|
|
1568
|
+
}
|
|
1569
|
+
if (set.size > 0) conditional.set(name, set);
|
|
1570
|
+
}
|
|
1571
|
+
for (const [i, [when, , , cut]] of table.c.entries()) {
|
|
1572
|
+
if (!cut) continue;
|
|
1573
|
+
const points = order.filter((c) => cut.some((name) => conditional.get(name)?.has(c)));
|
|
1574
|
+
if (points.length === 0) continue;
|
|
1575
|
+
const current = new Map(values);
|
|
1576
|
+
const held = ["", ...points].map((point) => {
|
|
1577
|
+
for (const name of cut) {
|
|
1578
|
+
const value = conditional.get(name)?.get(point);
|
|
1579
|
+
if (value !== void 0) current.set(name, value);
|
|
1580
|
+
}
|
|
1581
|
+
return Object.keys(when).every((name) => when[name].includes(current.get(name) ?? ""));
|
|
1582
|
+
});
|
|
1583
|
+
const breakpoint = (c) => order.indexOf(c) < breakpoints;
|
|
1584
|
+
if (breakpoint(points.at(-1))) continue;
|
|
1585
|
+
const nv = data.compoundVariants.length;
|
|
1586
|
+
const at = i < nv ? `compoundVariants.${i}` : `compoundSlots.${i - nv}`;
|
|
1587
|
+
if (breakpoint(points[0])) {
|
|
1588
|
+
if (!held.includes(true)) continue;
|
|
1589
|
+
throw fail("", `the compound at ${at} depends on variants set under both breakpoint and container conditions, which no condition combines`, "Give the variants it depends on conditions of one kind.");
|
|
1590
|
+
}
|
|
1591
|
+
let j = points.length;
|
|
1592
|
+
while (j >= 0 && held[j]) j--;
|
|
1593
|
+
if (held.slice(0, j + 1).includes(true)) throw fail("", `the compound at ${at} holds below a container size or between two, which no container condition expresses`, "Container conditions are lower bounds: a compound can follow them from a size on.");
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
//#endregion
|
|
1597
|
+
//#region src/core/scope.ts
|
|
1598
|
+
/**
|
|
1599
|
+
* Local name of an anonymous or expression default export, Rollup's
|
|
1600
|
+
* convention. It cannot clash with an identifier.
|
|
1601
|
+
*/
|
|
1602
|
+
const DEFAULT_LOCAL = "*default*";
|
|
1603
|
+
/** Array methods that change the array; any other built-in leaves its object alone. */
|
|
1604
|
+
const MUTATING_METHODS = /* @__PURE__ */ new Set([
|
|
1605
|
+
"push",
|
|
1606
|
+
"pop",
|
|
1607
|
+
"shift",
|
|
1608
|
+
"unshift",
|
|
1609
|
+
"splice",
|
|
1610
|
+
"sort",
|
|
1611
|
+
"reverse",
|
|
1612
|
+
"fill",
|
|
1613
|
+
"copyWithin",
|
|
1614
|
+
"__defineGetter__",
|
|
1615
|
+
"__defineSetter__"
|
|
1616
|
+
]);
|
|
1617
|
+
/** Keys that hold types or metadata, never values. */
|
|
1618
|
+
const SKIPPED_KEYS = /* @__PURE__ */ new Set([
|
|
1619
|
+
"parent",
|
|
1620
|
+
"range",
|
|
1621
|
+
"loc",
|
|
1622
|
+
"tokens",
|
|
1623
|
+
"comments",
|
|
1624
|
+
"typeAnnotation",
|
|
1625
|
+
"typeArguments",
|
|
1626
|
+
"typeParameters",
|
|
1627
|
+
"returnType",
|
|
1628
|
+
"superTypeArguments",
|
|
1629
|
+
"implements"
|
|
1630
|
+
]);
|
|
1631
|
+
function analyzeScopes(program) {
|
|
1632
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
1633
|
+
const references = [];
|
|
1634
|
+
const exports = /* @__PURE__ */ new Map();
|
|
1635
|
+
const starExports = [];
|
|
1636
|
+
const moduleScope = {
|
|
1637
|
+
parent: void 0,
|
|
1638
|
+
bindings: /* @__PURE__ */ new Map(),
|
|
1639
|
+
hoists: true
|
|
1640
|
+
};
|
|
1641
|
+
const writes = [];
|
|
1642
|
+
const holders = /* @__PURE__ */ new Map();
|
|
1643
|
+
/** The `const` bindings whose initializer is being visited, innermost last. */
|
|
1644
|
+
const initializing = [];
|
|
1645
|
+
/** `o`, `o.a.b`, `o[0]`, `(o as T).a`: the identifier at the root of a member chain. */
|
|
1646
|
+
const rootOf = (node) => {
|
|
1647
|
+
switch (node.type) {
|
|
1648
|
+
case "Identifier": return node;
|
|
1649
|
+
case "MemberExpression": return rootOf(node.object);
|
|
1650
|
+
case "ChainExpression":
|
|
1651
|
+
case "ParenthesizedExpression":
|
|
1652
|
+
case "TSAsExpression":
|
|
1653
|
+
case "TSSatisfiesExpression":
|
|
1654
|
+
case "TSNonNullExpression":
|
|
1655
|
+
case "TSTypeAssertion":
|
|
1656
|
+
case "TSInstantiationExpression": return rootOf(node.expression);
|
|
1657
|
+
default: return;
|
|
1658
|
+
}
|
|
1659
|
+
};
|
|
1660
|
+
/** Records a write to the object at the root of `target`, when `target` reaches into one. */
|
|
1661
|
+
const write = (target, node, call) => {
|
|
1662
|
+
const root = rootOf(target);
|
|
1663
|
+
if (root) writes.push([root, call ? {
|
|
1664
|
+
node,
|
|
1665
|
+
call
|
|
1666
|
+
} : { node }]);
|
|
1667
|
+
};
|
|
1668
|
+
const isMember = (node) => {
|
|
1669
|
+
const root = rootOf(node);
|
|
1670
|
+
return root !== void 0 && root !== node;
|
|
1671
|
+
};
|
|
1672
|
+
const child = (parent, hoists = false) => ({
|
|
1673
|
+
parent,
|
|
1674
|
+
bindings: /* @__PURE__ */ new Map(),
|
|
1675
|
+
hoists
|
|
1676
|
+
});
|
|
1677
|
+
const declare = (scope, binding) => {
|
|
1678
|
+
scope.bindings.set(binding.name, binding);
|
|
1679
|
+
if (binding.id) resolved.set(binding.id, binding);
|
|
1680
|
+
};
|
|
1681
|
+
const reference = (id, scope) => {
|
|
1682
|
+
references.push([id, scope]);
|
|
1683
|
+
if (initializing.length > 0) holders.set(id, [...initializing]);
|
|
1684
|
+
};
|
|
1685
|
+
/**
|
|
1686
|
+
* Walks a binding or assignment pattern: default values and computed keys
|
|
1687
|
+
* are expressions in `scope`, every identifier goes to `onId`.
|
|
1688
|
+
*/
|
|
1689
|
+
const walkPattern = (pattern, scope, onId, path) => {
|
|
1690
|
+
switch (pattern.type) {
|
|
1691
|
+
case "Identifier":
|
|
1692
|
+
onId(pattern, path);
|
|
1693
|
+
return;
|
|
1694
|
+
case "AssignmentPattern":
|
|
1695
|
+
visit(pattern.right, scope);
|
|
1696
|
+
walkPattern(pattern.left, scope, onId, path);
|
|
1697
|
+
return;
|
|
1698
|
+
case "RestElement":
|
|
1699
|
+
walkPattern(pattern.argument, scope, onId, void 0);
|
|
1700
|
+
return;
|
|
1701
|
+
case "ObjectPattern":
|
|
1702
|
+
for (const property of pattern.properties) {
|
|
1703
|
+
if (property.type === "RestElement") {
|
|
1704
|
+
walkPattern(property, scope, onId, void 0);
|
|
1705
|
+
continue;
|
|
1706
|
+
}
|
|
1707
|
+
if (property.computed) visit(property.key, scope);
|
|
1708
|
+
const key = literalKey(property.key, property.computed);
|
|
1709
|
+
walkPattern(property.value, scope, onId, path && key !== void 0 ? [...path, key] : void 0);
|
|
1710
|
+
}
|
|
1711
|
+
return;
|
|
1712
|
+
case "ArrayPattern":
|
|
1713
|
+
pattern.elements.forEach((element, index) => {
|
|
1714
|
+
if (element) walkPattern(element, scope, onId, path && [...path, index]);
|
|
1715
|
+
});
|
|
1716
|
+
return;
|
|
1717
|
+
case "TSParameterProperty":
|
|
1718
|
+
walkPattern(pattern.parameter, scope, onId, path);
|
|
1719
|
+
return;
|
|
1720
|
+
default: visit(pattern, scope);
|
|
1721
|
+
}
|
|
1722
|
+
};
|
|
1723
|
+
const assignTarget = (target, scope, node) => {
|
|
1724
|
+
const members = [];
|
|
1725
|
+
const collect = (pattern) => {
|
|
1726
|
+
switch (pattern.type) {
|
|
1727
|
+
case "AssignmentPattern":
|
|
1728
|
+
collect(pattern.left);
|
|
1729
|
+
return;
|
|
1730
|
+
case "RestElement":
|
|
1731
|
+
collect(pattern.argument);
|
|
1732
|
+
return;
|
|
1733
|
+
case "ObjectPattern":
|
|
1734
|
+
for (const p of pattern.properties) collect(p.type === "RestElement" ? p : p.value);
|
|
1735
|
+
return;
|
|
1736
|
+
case "ArrayPattern":
|
|
1737
|
+
for (const element of pattern.elements) if (element) collect(element);
|
|
1738
|
+
return;
|
|
1739
|
+
default: if (isMember(pattern)) members.push(pattern);
|
|
1740
|
+
}
|
|
1741
|
+
};
|
|
1742
|
+
collect(target);
|
|
1743
|
+
for (const member of members) write(member, node);
|
|
1744
|
+
walkPattern(target, scope, (id) => reference(id, scope), void 0);
|
|
1745
|
+
};
|
|
1746
|
+
const declareVariables = (declaration, scope) => {
|
|
1747
|
+
const kind = declaration.kind === "var" || declaration.kind === "let" ? declaration.kind : "const";
|
|
1748
|
+
const disposable = declaration.kind === "using" || declaration.kind === "await using";
|
|
1749
|
+
const target = kind === "var" ? hoistScope(scope) : scope;
|
|
1750
|
+
for (const declarator of declaration.declarations) {
|
|
1751
|
+
const init = disposable ? void 0 : declarator.init ?? void 0;
|
|
1752
|
+
const declared = [];
|
|
1753
|
+
walkPattern(declarator.id, scope, (id, path) => {
|
|
1754
|
+
const binding = {
|
|
1755
|
+
kind,
|
|
1756
|
+
name: id.name,
|
|
1757
|
+
node: declarator,
|
|
1758
|
+
id,
|
|
1759
|
+
init,
|
|
1760
|
+
path: nonEmpty(path)
|
|
1761
|
+
};
|
|
1762
|
+
declare(target, binding);
|
|
1763
|
+
declared.push(binding);
|
|
1764
|
+
}, []);
|
|
1765
|
+
if (!declarator.init) continue;
|
|
1766
|
+
if (kind === "const") initializing.push(...declared);
|
|
1767
|
+
visit(declarator.init, scope);
|
|
1768
|
+
if (kind === "const") initializing.length -= declared.length;
|
|
1769
|
+
}
|
|
1770
|
+
};
|
|
1771
|
+
const visitFunction = (fn, scope) => {
|
|
1772
|
+
let outer = scope;
|
|
1773
|
+
if (fn.type === "FunctionExpression" && fn.id) {
|
|
1774
|
+
outer = child(scope);
|
|
1775
|
+
declare(outer, {
|
|
1776
|
+
kind: "function",
|
|
1777
|
+
name: fn.id.name,
|
|
1778
|
+
node: fn,
|
|
1779
|
+
id: fn.id
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
const params = child(outer);
|
|
1783
|
+
fn.params.forEach((param, index) => {
|
|
1784
|
+
if ("decorators" in param && param.decorators) visitAll(param.decorators, params);
|
|
1785
|
+
if (param.type === "Identifier" && param.name === "this") return;
|
|
1786
|
+
walkPattern(param, params, (id, path) => declare(params, {
|
|
1787
|
+
kind: "param",
|
|
1788
|
+
name: id.name,
|
|
1789
|
+
node: fn,
|
|
1790
|
+
id,
|
|
1791
|
+
path
|
|
1792
|
+
}), [index]);
|
|
1793
|
+
});
|
|
1794
|
+
if (!fn.body) return;
|
|
1795
|
+
if (fn.body.type === "BlockStatement") visitAll(fn.body.body, child(params, true));
|
|
1796
|
+
else visit(fn.body, params);
|
|
1797
|
+
};
|
|
1798
|
+
const visitClass = (cls, scope) => {
|
|
1799
|
+
if (cls.type === "ClassDeclaration" && cls.id) declare(scope, {
|
|
1800
|
+
kind: "class",
|
|
1801
|
+
name: cls.id.name,
|
|
1802
|
+
node: cls,
|
|
1803
|
+
id: cls.id
|
|
1804
|
+
});
|
|
1805
|
+
visitAll(cls.decorators, scope);
|
|
1806
|
+
if (cls.superClass) visit(cls.superClass, scope);
|
|
1807
|
+
const inner = child(scope);
|
|
1808
|
+
if (cls.type === "ClassExpression" && cls.id) declare(inner, {
|
|
1809
|
+
kind: "class",
|
|
1810
|
+
name: cls.id.name,
|
|
1811
|
+
node: cls,
|
|
1812
|
+
id: cls.id
|
|
1813
|
+
});
|
|
1814
|
+
visitAll(cls.body.body, inner);
|
|
1815
|
+
};
|
|
1816
|
+
const exportDeclaration = (node, scope) => {
|
|
1817
|
+
if (node.exportKind === "type") return;
|
|
1818
|
+
const declaration = node.declaration;
|
|
1819
|
+
if (declaration) {
|
|
1820
|
+
visit(declaration, scope);
|
|
1821
|
+
if ("declare" in declaration && declaration.declare) return;
|
|
1822
|
+
for (const name of declaredNames(declaration)) exports.set(name, { local: name });
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
const source = node.source?.value;
|
|
1826
|
+
for (const specifier of node.specifiers) {
|
|
1827
|
+
if (specifier.exportKind === "type") continue;
|
|
1828
|
+
const local = exportName(specifier.local);
|
|
1829
|
+
const exported = exportName(specifier.exported);
|
|
1830
|
+
if (source !== void 0) exports.set(exported, {
|
|
1831
|
+
source,
|
|
1832
|
+
imported: local
|
|
1833
|
+
});
|
|
1834
|
+
else {
|
|
1835
|
+
exports.set(exported, { local });
|
|
1836
|
+
if (specifier.local.type === "Identifier") reference(specifier.local, scope);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
};
|
|
1840
|
+
const exportDefault = (node, scope) => {
|
|
1841
|
+
const declaration = node.declaration;
|
|
1842
|
+
switch (declaration.type) {
|
|
1843
|
+
case "TSInterfaceDeclaration": return;
|
|
1844
|
+
case "Identifier":
|
|
1845
|
+
reference(declaration, scope);
|
|
1846
|
+
exports.set("default", { local: declaration.name });
|
|
1847
|
+
return;
|
|
1848
|
+
case "FunctionDeclaration":
|
|
1849
|
+
case "ClassDeclaration":
|
|
1850
|
+
if (!declaration.id) {
|
|
1851
|
+
const kind = declaration.type === "ClassDeclaration" ? "class" : "function";
|
|
1852
|
+
declare(scope, {
|
|
1853
|
+
kind,
|
|
1854
|
+
name: DEFAULT_LOCAL,
|
|
1855
|
+
node: declaration
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
visit(declaration, scope);
|
|
1859
|
+
exports.set("default", { local: declaration.id?.name ?? "*default*" });
|
|
1860
|
+
return;
|
|
1861
|
+
default:
|
|
1862
|
+
declare(scope, {
|
|
1863
|
+
kind: "const",
|
|
1864
|
+
name: DEFAULT_LOCAL,
|
|
1865
|
+
node,
|
|
1866
|
+
init: declaration
|
|
1867
|
+
});
|
|
1868
|
+
visit(declaration, scope);
|
|
1869
|
+
exports.set("default", { local: DEFAULT_LOCAL });
|
|
1870
|
+
}
|
|
1871
|
+
};
|
|
1872
|
+
const visitAll = (nodes, scope) => {
|
|
1873
|
+
if (nodes) {
|
|
1874
|
+
for (const node of nodes) if (node) visit(node, scope);
|
|
1875
|
+
}
|
|
1876
|
+
};
|
|
1877
|
+
const visit = (node, scope) => {
|
|
1878
|
+
switch (node.type) {
|
|
1879
|
+
case "Identifier":
|
|
1880
|
+
reference(node, scope);
|
|
1881
|
+
return;
|
|
1882
|
+
case "VariableDeclaration":
|
|
1883
|
+
declareVariables(node, scope);
|
|
1884
|
+
return;
|
|
1885
|
+
case "FunctionDeclaration":
|
|
1886
|
+
case "TSDeclareFunction":
|
|
1887
|
+
if (node.id) declare(scope, {
|
|
1888
|
+
kind: "function",
|
|
1889
|
+
name: node.id.name,
|
|
1890
|
+
node,
|
|
1891
|
+
id: node.id
|
|
1892
|
+
});
|
|
1893
|
+
visitFunction(node, scope);
|
|
1894
|
+
return;
|
|
1895
|
+
case "FunctionExpression":
|
|
1896
|
+
case "ArrowFunctionExpression":
|
|
1897
|
+
visitFunction(node, scope);
|
|
1898
|
+
return;
|
|
1899
|
+
case "ClassDeclaration":
|
|
1900
|
+
case "ClassExpression":
|
|
1901
|
+
visitClass(node, scope);
|
|
1902
|
+
return;
|
|
1903
|
+
case "BlockStatement":
|
|
1904
|
+
visitAll(node.body, child(scope));
|
|
1905
|
+
return;
|
|
1906
|
+
case "StaticBlock":
|
|
1907
|
+
visitAll(node.body, child(scope, true));
|
|
1908
|
+
return;
|
|
1909
|
+
case "ForStatement": {
|
|
1910
|
+
const loop = child(scope);
|
|
1911
|
+
if (node.init) visit(node.init, loop);
|
|
1912
|
+
if (node.test) visit(node.test, loop);
|
|
1913
|
+
if (node.update) visit(node.update, loop);
|
|
1914
|
+
visit(node.body, loop);
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
case "ForInStatement":
|
|
1918
|
+
case "ForOfStatement": {
|
|
1919
|
+
const loop = child(scope);
|
|
1920
|
+
if (node.left.type === "VariableDeclaration") visit(node.left, loop);
|
|
1921
|
+
else assignTarget(node.left, loop, node);
|
|
1922
|
+
visit(node.right, loop);
|
|
1923
|
+
visit(node.body, loop);
|
|
1924
|
+
return;
|
|
1925
|
+
}
|
|
1926
|
+
case "SwitchStatement": {
|
|
1927
|
+
visit(node.discriminant, scope);
|
|
1928
|
+
const cases = child(scope);
|
|
1929
|
+
for (const switchCase of node.cases) {
|
|
1930
|
+
if (switchCase.test) visit(switchCase.test, cases);
|
|
1931
|
+
visitAll(switchCase.consequent, cases);
|
|
1932
|
+
}
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1935
|
+
case "CatchClause": {
|
|
1936
|
+
const clause = child(scope);
|
|
1937
|
+
if (node.param) walkPattern(node.param, clause, (id, path) => declare(clause, {
|
|
1938
|
+
kind: "catch",
|
|
1939
|
+
name: id.name,
|
|
1940
|
+
node,
|
|
1941
|
+
id,
|
|
1942
|
+
path: nonEmpty(path)
|
|
1943
|
+
}), []);
|
|
1944
|
+
visit(node.body, clause);
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
case "LabeledStatement":
|
|
1948
|
+
visit(node.body, scope);
|
|
1949
|
+
return;
|
|
1950
|
+
case "BreakStatement":
|
|
1951
|
+
case "ContinueStatement":
|
|
1952
|
+
case "MetaProperty": return;
|
|
1953
|
+
case "AssignmentExpression":
|
|
1954
|
+
assignTarget(node.left, scope, node);
|
|
1955
|
+
visit(node.right, scope);
|
|
1956
|
+
return;
|
|
1957
|
+
case "UpdateExpression":
|
|
1958
|
+
if (isMember(node.argument)) write(node.argument, node);
|
|
1959
|
+
visit(node.argument, scope);
|
|
1960
|
+
return;
|
|
1961
|
+
case "UnaryExpression":
|
|
1962
|
+
if (node.operator === "delete" && isMember(node.argument)) write(node.argument, node);
|
|
1963
|
+
visit(node.argument, scope);
|
|
1964
|
+
return;
|
|
1965
|
+
case "CallExpression":
|
|
1966
|
+
case "NewExpression": {
|
|
1967
|
+
const callee = unwrapExpression(node.callee);
|
|
1968
|
+
if (node.type === "CallExpression" && callee.type === "MemberExpression" && (callee.computed || MUTATING_METHODS.has(callee.property.name))) write(callee.object, node);
|
|
1969
|
+
for (const argument of node.arguments) {
|
|
1970
|
+
const value = argument.type === "SpreadElement" ? argument.argument : argument;
|
|
1971
|
+
if (node.type === "CallExpression") write(value, argument, node);
|
|
1972
|
+
else write(value, argument);
|
|
1973
|
+
}
|
|
1974
|
+
visit(node.callee, scope);
|
|
1975
|
+
visitAll(node.arguments, scope);
|
|
1976
|
+
return;
|
|
1977
|
+
}
|
|
1978
|
+
case "MemberExpression":
|
|
1979
|
+
visit(node.object, scope);
|
|
1980
|
+
if (node.computed) visit(node.property, scope);
|
|
1981
|
+
return;
|
|
1982
|
+
case "Property":
|
|
1983
|
+
if (node.computed) visit(node.key, scope);
|
|
1984
|
+
visit(node.value, scope);
|
|
1985
|
+
return;
|
|
1986
|
+
case "MethodDefinition":
|
|
1987
|
+
case "PropertyDefinition":
|
|
1988
|
+
case "AccessorProperty":
|
|
1989
|
+
visitAll(node.decorators, scope);
|
|
1990
|
+
if (node.computed) visit(node.key, scope);
|
|
1991
|
+
if (node.value) visit(node.value, scope);
|
|
1992
|
+
return;
|
|
1993
|
+
case "V8IntrinsicExpression":
|
|
1994
|
+
visitAll(node.arguments, scope);
|
|
1995
|
+
return;
|
|
1996
|
+
case "ImportDeclaration":
|
|
1997
|
+
if (node.importKind === "type") return;
|
|
1998
|
+
for (const specifier of node.specifiers) {
|
|
1999
|
+
if (specifier.type === "ImportSpecifier" && specifier.importKind === "type") continue;
|
|
2000
|
+
declare(moduleScope, {
|
|
2001
|
+
kind: "import",
|
|
2002
|
+
name: specifier.local.name,
|
|
2003
|
+
node: specifier,
|
|
2004
|
+
id: specifier.local,
|
|
2005
|
+
source: node.source.value,
|
|
2006
|
+
imported: specifier.type === "ImportDefaultSpecifier" ? "default" : specifier.type === "ImportNamespaceSpecifier" ? "*" : exportName(specifier.imported)
|
|
2007
|
+
});
|
|
2008
|
+
}
|
|
2009
|
+
return;
|
|
2010
|
+
case "ExportNamedDeclaration":
|
|
2011
|
+
exportDeclaration(node, scope);
|
|
2012
|
+
return;
|
|
2013
|
+
case "ExportDefaultDeclaration":
|
|
2014
|
+
exportDefault(node, scope);
|
|
2015
|
+
return;
|
|
2016
|
+
case "ExportAllDeclaration":
|
|
2017
|
+
if (node.exportKind === "type") return;
|
|
2018
|
+
if (node.exported) exports.set(exportName(node.exported), {
|
|
2019
|
+
source: node.source.value,
|
|
2020
|
+
imported: "*"
|
|
2021
|
+
});
|
|
2022
|
+
else starExports.push(node.source.value);
|
|
2023
|
+
return;
|
|
2024
|
+
case "TSEnumDeclaration":
|
|
2025
|
+
declare(scope, {
|
|
2026
|
+
kind: "const",
|
|
2027
|
+
name: node.id.name,
|
|
2028
|
+
node,
|
|
2029
|
+
id: node.id
|
|
2030
|
+
});
|
|
2031
|
+
return;
|
|
2032
|
+
case "TSModuleDeclaration": {
|
|
2033
|
+
if (node.declare || node.kind === "global" || node.id.type === "Literal") return;
|
|
2034
|
+
let id = node.id;
|
|
2035
|
+
while (id.type === "TSQualifiedName") id = id.left;
|
|
2036
|
+
if (id.type === "Identifier") declare(scope, {
|
|
2037
|
+
kind: "const",
|
|
2038
|
+
name: id.name,
|
|
2039
|
+
node,
|
|
2040
|
+
id
|
|
2041
|
+
});
|
|
2042
|
+
if (node.body) visit(node.body, child(scope, true));
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
case "TSModuleBlock":
|
|
2046
|
+
visitAll(node.body, scope);
|
|
2047
|
+
return;
|
|
2048
|
+
case "TSImportEqualsDeclaration":
|
|
2049
|
+
if (node.importKind !== "type") declare(scope, {
|
|
2050
|
+
kind: "const",
|
|
2051
|
+
name: node.id.name,
|
|
2052
|
+
node,
|
|
2053
|
+
id: node.id
|
|
2054
|
+
});
|
|
2055
|
+
return;
|
|
2056
|
+
case "TSAsExpression":
|
|
2057
|
+
case "TSSatisfiesExpression":
|
|
2058
|
+
case "TSTypeAssertion":
|
|
2059
|
+
case "TSNonNullExpression":
|
|
2060
|
+
case "TSInstantiationExpression":
|
|
2061
|
+
case "TSExportAssignment":
|
|
2062
|
+
visit(node.expression, scope);
|
|
2063
|
+
return;
|
|
2064
|
+
default:
|
|
2065
|
+
if (node.type.startsWith("TS")) return;
|
|
2066
|
+
visitChildren(node, scope);
|
|
2067
|
+
}
|
|
2068
|
+
};
|
|
2069
|
+
const visitChildren = (node, scope) => {
|
|
2070
|
+
const record = node;
|
|
2071
|
+
for (const key in record) {
|
|
2072
|
+
if (SKIPPED_KEYS.has(key)) continue;
|
|
2073
|
+
const value = record[key];
|
|
2074
|
+
if (Array.isArray(value)) {
|
|
2075
|
+
for (const item of value) if (isNode(item)) visit(item, scope);
|
|
2076
|
+
} else if (isNode(value)) visit(value, scope);
|
|
2077
|
+
}
|
|
2078
|
+
};
|
|
2079
|
+
visitAll(program.body, moduleScope);
|
|
2080
|
+
for (const [id, scope] of references) for (let s = scope; s; s = s.parent) {
|
|
2081
|
+
const binding = s.bindings.get(id.name);
|
|
2082
|
+
if (binding) {
|
|
2083
|
+
resolved.set(id, binding);
|
|
2084
|
+
break;
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
const writesByBinding = /* @__PURE__ */ new Map();
|
|
2088
|
+
for (const [id, entry] of writes) {
|
|
2089
|
+
const binding = resolved.get(id);
|
|
2090
|
+
if (binding) writesByBinding.set(binding, [...writesByBinding.get(binding) ?? [], entry]);
|
|
2091
|
+
}
|
|
2092
|
+
const holdersByBinding = /* @__PURE__ */ new Map();
|
|
2093
|
+
for (const [id, within] of holders) {
|
|
2094
|
+
const binding = resolved.get(id);
|
|
2095
|
+
if (!binding) continue;
|
|
2096
|
+
const list = holdersByBinding.get(binding) ?? [];
|
|
2097
|
+
for (const holder of within) if (holder !== binding && !list.includes(holder)) list.push(holder);
|
|
2098
|
+
holdersByBinding.set(binding, list);
|
|
2099
|
+
}
|
|
2100
|
+
return {
|
|
2101
|
+
bindingOf: (id) => resolved.get(id),
|
|
2102
|
+
moduleBindings: moduleScope.bindings,
|
|
2103
|
+
exports,
|
|
2104
|
+
starExports,
|
|
2105
|
+
writesOf: (binding) => writesByBinding.get(binding) ?? [],
|
|
2106
|
+
holdersOf: (binding) => holdersByBinding.get(binding) ?? []
|
|
2107
|
+
};
|
|
2108
|
+
}
|
|
2109
|
+
function unwrapExpression(node) {
|
|
2110
|
+
let inner = node;
|
|
2111
|
+
while (inner.type === "ParenthesizedExpression" || inner.type === "TSAsExpression" || inner.type === "TSSatisfiesExpression" || inner.type === "TSNonNullExpression" || inner.type === "TSTypeAssertion" || inner.type === "TSInstantiationExpression" || inner.type === "ChainExpression") inner = inner.expression;
|
|
2112
|
+
return inner;
|
|
2113
|
+
}
|
|
2114
|
+
function hoistScope(scope) {
|
|
2115
|
+
let s = scope;
|
|
2116
|
+
while (!s.hoists && s.parent) s = s.parent;
|
|
2117
|
+
return s;
|
|
2118
|
+
}
|
|
2119
|
+
function nonEmpty(path) {
|
|
2120
|
+
return path && path.length > 0 ? path : void 0;
|
|
2121
|
+
}
|
|
2122
|
+
function isNode(value) {
|
|
2123
|
+
return typeof value === "object" && value !== null && typeof value.type === "string";
|
|
2124
|
+
}
|
|
2125
|
+
function exportName(name) {
|
|
2126
|
+
return name.type === "Literal" ? name.value : name.name;
|
|
2127
|
+
}
|
|
2128
|
+
/** Key of a pattern property when it is known without evaluation. */
|
|
2129
|
+
function literalKey(key, computed) {
|
|
2130
|
+
if (key.type === "Identifier" && !computed) return key.name;
|
|
2131
|
+
if (key.type === "Literal" && (typeof key.value === "string" || typeof key.value === "number")) return String(key.value);
|
|
2132
|
+
}
|
|
2133
|
+
/** Names a declaration binds, for `export <declaration>`. */
|
|
2134
|
+
function declaredNames(declaration) {
|
|
2135
|
+
switch (declaration.type) {
|
|
2136
|
+
case "VariableDeclaration": return declaration.declarations.flatMap((d) => patternNames(d.id));
|
|
2137
|
+
case "FunctionDeclaration":
|
|
2138
|
+
case "ClassDeclaration":
|
|
2139
|
+
case "TSEnumDeclaration": return declaration.id ? [declaration.id.name] : [];
|
|
2140
|
+
case "TSModuleDeclaration": return declaration.id.type === "Identifier" ? [declaration.id.name] : [];
|
|
2141
|
+
default: return [];
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
function patternNames(pattern) {
|
|
2145
|
+
switch (pattern.type) {
|
|
2146
|
+
case "Identifier": return [pattern.name];
|
|
2147
|
+
case "AssignmentPattern": return patternNames(pattern.left);
|
|
2148
|
+
case "RestElement": return patternNames(pattern.argument);
|
|
2149
|
+
case "ObjectPattern": return pattern.properties.flatMap((p) => patternNames(p.type === "RestElement" ? p : p.value));
|
|
2150
|
+
case "ArrayPattern": return pattern.elements.flatMap((e) => e ? patternNames(e) : []);
|
|
2151
|
+
default: return [];
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
//#endregion
|
|
2155
|
+
//#region src/core/theme.ts
|
|
2156
|
+
/**
|
|
2157
|
+
* Scoped themes (PLAN §4.8, §5.9): `theme({ colors: { accent: "{colors.red.600}" } })`
|
|
2158
|
+
* compiles to a class that overrides tokens for a subtree, in `@layer
|
|
2159
|
+
* vicss.tokens` after the root rules.
|
|
2160
|
+
*
|
|
2161
|
+
* Custom properties resolve `var()` where they are declared, and descendants
|
|
2162
|
+
* inherit the result. So every element where a token's inputs change must
|
|
2163
|
+
* declare the token again: the root and each mode scope do for switching
|
|
2164
|
+
* tokens (`printTokens`), and a theme does on its own element, for the tokens
|
|
2165
|
+
* it overrides and every token that depends on them. Three sets of variables
|
|
2166
|
+
* make this compose with modes and with other themes:
|
|
2167
|
+
*
|
|
2168
|
+
* - `--x_T` and `--x_T-<mode>`: the theme's values for token `x`, set only on
|
|
2169
|
+
* the theme's element (`initial` for a mode it gives no value), so they
|
|
2170
|
+
* inherit to the subtree and a nested theme replaces them;
|
|
2171
|
+
* - `--x_S-<mode>`: `var(--mode_<mode>) var(--x_T-<mode>)`, the theme's value
|
|
2172
|
+
* where the mode holds and invalid elsewhere, read from the mode's flag
|
|
2173
|
+
* (`modeFlag`) since the token's own mode values are what the theme replaces;
|
|
2174
|
+
* - `--x`: the selection `var(--x_S-<highest>, …, var(--x_T, <the token's own
|
|
2175
|
+
* value>))`, highest-ranked mode first like the root rules.
|
|
2176
|
+
*
|
|
2177
|
+
* A mode scope inside the theme recomputes `--x` from its own mode values, so
|
|
2178
|
+
* the theme also sets `--x_S-*` and `--x` on the scopes inside its element.
|
|
2179
|
+
* Those declarations depend only on the token, never on the theme, so when
|
|
2180
|
+
* nested themes' rules match one scope, whichever wins sets the same thing;
|
|
2181
|
+
* the nearest theme's `--x_T*` decide the value.
|
|
2182
|
+
*
|
|
2183
|
+
* A reference in a theme value is read once, where the theme applies:
|
|
2184
|
+
* `theme({ colors: { button: "{colors.brand}" } })` gives `button` the brand
|
|
2185
|
+
* of the theme's element. A nested theme that overrides `brand` changes
|
|
2186
|
+
* every config alias of `brand` below it (they are declared again on its
|
|
2187
|
+
* element), but not the outer theme's `button`: CSS keeps no unresolved
|
|
2188
|
+
* `var()` to inherit, and one theme cannot know what another references.
|
|
2189
|
+
*/
|
|
2190
|
+
/**
|
|
2191
|
+
* The merge key of theme classes: `sx(a, b)` keeps the last theme, like any
|
|
2192
|
+
* property. Two themes on one element would both set the custom properties
|
|
2193
|
+
* where their tokens meet (one token, or an alias each declares again), and
|
|
2194
|
+
* the sheet's order would pick the winner rather than the call's; nested
|
|
2195
|
+
* elements, or one theme with both overrides, compose instead. A folded
|
|
2196
|
+
* call that drops a theme warns (compile.ts), and so does a runtime call in
|
|
2197
|
+
* dev (runtime/dev.ts).
|
|
2198
|
+
*/
|
|
2199
|
+
const THEME_KEY = "vx-theme";
|
|
2200
|
+
const VAR = /var\((--[A-Za-z0-9_-]+)\)/g;
|
|
2201
|
+
/** The theme's value for `token`, its value per mode, and where the selection lands. */
|
|
2202
|
+
const themeVar = (token) => `${token.cssVar}_T`;
|
|
2203
|
+
const themeModeVar = (token, mode) => `${token.cssVar}_T-${mode}`;
|
|
2204
|
+
const selectedVar = (token, mode) => `${token.cssVar}_S-${mode}`;
|
|
2205
|
+
const graphs = /* @__PURE__ */ new WeakMap();
|
|
2206
|
+
function graphOf(system) {
|
|
2207
|
+
let graph = graphs.get(system);
|
|
2208
|
+
if (graph) return graph;
|
|
2209
|
+
const tokens = [...system.tokens.values()].flatMap((table) => [...table.values()].filter((t) => !t.palette));
|
|
2210
|
+
const byVar = new Map(tokens.map((t) => [t.cssVar, t]));
|
|
2211
|
+
const refs = /* @__PURE__ */ new Map();
|
|
2212
|
+
const dependents = new Map(tokens.map((t) => [t, /* @__PURE__ */ new Set()]));
|
|
2213
|
+
for (const token of tokens) {
|
|
2214
|
+
const own = /* @__PURE__ */ new Set();
|
|
2215
|
+
for (const value of [token.value, ...Object.values(token.modes ?? {})]) for (const target of referencedTokens(value, byVar)) own.add(target);
|
|
2216
|
+
refs.set(token, own);
|
|
2217
|
+
for (const target of own) dependents.get(target).add(token);
|
|
2218
|
+
}
|
|
2219
|
+
const modes = scopedModes(system);
|
|
2220
|
+
const valued = modes.filter((mode) => tokens.some((t) => t.modes?.[mode.name] !== void 0));
|
|
2221
|
+
const scopes = /* @__PURE__ */ new Set();
|
|
2222
|
+
for (const mode of modes) {
|
|
2223
|
+
if (!valued.some((other) => other === mode || exclusive(mode, other))) continue;
|
|
2224
|
+
for (const scope of mode.scope ?? []) if (scope.selector !== void 0 && !isRootScope(scope.selector)) scopes.add(scope.selector);
|
|
2225
|
+
}
|
|
2226
|
+
graph = {
|
|
2227
|
+
tokens,
|
|
2228
|
+
order: new Map(tokens.map((t, i) => [t, i])),
|
|
2229
|
+
byVar,
|
|
2230
|
+
refs,
|
|
2231
|
+
dependents,
|
|
2232
|
+
switching: switchingTokens(tokens),
|
|
2233
|
+
modes,
|
|
2234
|
+
scopes: [...scopes],
|
|
2235
|
+
themeModes: /* @__PURE__ */ new Map()
|
|
2236
|
+
};
|
|
2237
|
+
graphs.set(system, graph);
|
|
2238
|
+
return graph;
|
|
2239
|
+
}
|
|
2240
|
+
function referencedTokens(value, byVar) {
|
|
2241
|
+
return [...value.matchAll(VAR)].flatMap(([, name]) => {
|
|
2242
|
+
const token = byVar.get(name);
|
|
2243
|
+
return token ? [token] : [];
|
|
2244
|
+
});
|
|
2245
|
+
}
|
|
2246
|
+
function themeModes(graph, token) {
|
|
2247
|
+
let modes = graph.themeModes.get(token);
|
|
2248
|
+
if (modes) return modes;
|
|
2249
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2250
|
+
const names = /* @__PURE__ */ new Set();
|
|
2251
|
+
const visit = (t) => {
|
|
2252
|
+
if (seen.has(t)) return;
|
|
2253
|
+
seen.add(t);
|
|
2254
|
+
for (const name of Object.keys(t.modes ?? {})) names.add(name);
|
|
2255
|
+
for (const next of graph.refs.get(t) ?? []) visit(next);
|
|
2256
|
+
};
|
|
2257
|
+
visit(token);
|
|
2258
|
+
modes = graph.modes.filter((mode) => names.has(mode.name));
|
|
2259
|
+
graph.themeModes.set(token, modes);
|
|
2260
|
+
return modes;
|
|
2261
|
+
}
|
|
2262
|
+
/**
|
|
2263
|
+
* `theme(overrides)`: the class `vx-theme_<hash>` and its rule. `overrides`
|
|
2264
|
+
* is `{ scale: { path: value | { base, _mode } } }`, by nested groups or
|
|
2265
|
+
* dotted paths; values are CSS values and may reference tokens, which take
|
|
2266
|
+
* their value on the theme's element (a nested theme that overrides the
|
|
2267
|
+
* referenced token leaves this override as it was; see the file comment).
|
|
2268
|
+
*/
|
|
2269
|
+
function compileTheme(overrides, system, options = {}) {
|
|
2270
|
+
const graph = graphOf(system);
|
|
2271
|
+
const error = (code, at, message, hint) => new VicssError(code, at ? `${at}: ${message}` : message, {
|
|
2272
|
+
loc: options.locate?.(at),
|
|
2273
|
+
hint
|
|
2274
|
+
});
|
|
2275
|
+
if (!isPlainObject(overrides)) throw error("BAD_CALL", "", "theme() takes token overrides by scale", "Write theme({ colors: { accent: \"{colors.red.600}\" } }).");
|
|
2276
|
+
const found = /* @__PURE__ */ new Map();
|
|
2277
|
+
const value = (raw, at) => {
|
|
2278
|
+
let css;
|
|
2279
|
+
let refs = [];
|
|
2280
|
+
if (typeof raw === "number") {
|
|
2281
|
+
if (!Number.isFinite(raw)) throw error("BAD_VALUE", at, `${raw} is not a CSS value`);
|
|
2282
|
+
css = String(raw);
|
|
2283
|
+
} else if (typeof raw === "string") {
|
|
2284
|
+
if (raw.trim() === "") throw error("BAD_VALUE", at, "an empty string is not a CSS value");
|
|
2285
|
+
try {
|
|
2286
|
+
({value: css, tokens: refs} = resolveReferences(system, raw.trim()));
|
|
2287
|
+
} catch (e) {
|
|
2288
|
+
if (e instanceof VicssError) throw error(e.code, at, e.message, e.hint);
|
|
2289
|
+
throw e;
|
|
2290
|
+
}
|
|
2291
|
+
} else throw error("BAD_VALUE", at, "expected a CSS value: a string or a number");
|
|
2292
|
+
const bad = malformed(css);
|
|
2293
|
+
if (bad) throw error("BAD_VALUE", at, `\`${css}\` is not a CSS value: ${bad}`);
|
|
2294
|
+
const tokens = refs.map((ref) => lookupToken(system, ref.scale, ref.path));
|
|
2295
|
+
const palette = tokens.find((token) => token.palette);
|
|
2296
|
+
if (palette) throw error("BAD_VALUE", at, `\`{colors.${palette.path}}\` follows colorPalette, and a theme value takes the value it has where the theme applies`, "Name the palette's own token ({colors.blue.600}), or set colorPalette where the colour should follow it.");
|
|
2297
|
+
const switching = tokens.find((token) => graph.switching.has(token));
|
|
2298
|
+
if (switching) throw error("BAD_VALUE", at, `\`{${switching.scale}.${switching.path}}\` switches with modes, and a theme value takes the value it has where the theme applies`, "Give the override a value per mode instead: { base: …, _dark: … }.");
|
|
2299
|
+
return {
|
|
2300
|
+
css,
|
|
2301
|
+
refs: tokens
|
|
2302
|
+
};
|
|
2303
|
+
};
|
|
2304
|
+
const override = (scale, path, node, at) => {
|
|
2305
|
+
const token = lookupToken(system, scale, path);
|
|
2306
|
+
if (!token) throw error("UNKNOWN_TOKEN", at, `\`${scale}.${path}\` names no token`, didYouMean(suggestTokens(system, scale, path).map((p) => `${scale}.${p}`)));
|
|
2307
|
+
if (token.palette) throw error("BAD_VALUE", at, `\`colors.${token.path}\` is a palette key, which colorPalette points at a palette's token`, "Override the palette's own tokens (colors.blue.600), or set colorPalette on the element.");
|
|
2308
|
+
const first = found.get(token);
|
|
2309
|
+
if (first) throw error("BAD_VALUE", at, `overrides \`${scale}.${token.path}\` again (${first.at})`);
|
|
2310
|
+
const switchesWith = themeModes(graph, token);
|
|
2311
|
+
const modes = /* @__PURE__ */ new Map();
|
|
2312
|
+
const refs = [];
|
|
2313
|
+
let base;
|
|
2314
|
+
if (isPlainObject(node)) {
|
|
2315
|
+
for (const [key, inner] of Object.entries(node)) {
|
|
2316
|
+
if (inner === void 0) continue;
|
|
2317
|
+
const here = `${at}.${key}`;
|
|
2318
|
+
if (key === "base") {
|
|
2319
|
+
const v = value(inner, here);
|
|
2320
|
+
base = v.css;
|
|
2321
|
+
refs.push(...v.refs);
|
|
2322
|
+
continue;
|
|
2323
|
+
}
|
|
2324
|
+
const mode = key.slice(1);
|
|
2325
|
+
if (!key.startsWith("_") || !system.modes.has(mode)) {
|
|
2326
|
+
const known = [...system.modes].map((m) => `_${m}`);
|
|
2327
|
+
throw error(key.startsWith("_") ? "UNKNOWN_CONDITION" : "BAD_VALUE", here, `\`${key}\` is not a mode`, didYouMean(closestNames(key, ["base", ...known])) ?? `A value per mode looks like { base: …, ${known[0] ?? "_dark"}: … }.`);
|
|
2328
|
+
}
|
|
2329
|
+
if (!switchesWith.some((def) => def.name === mode)) throw error("BAD_VALUE", here, `\`${scale}.${token.path}\` does not switch with \`${key}\`, so a theme cannot give it a value there`, `Give the token a \`${key}\` value in semanticTokens first; a theme overrides the modes a token has.`);
|
|
2330
|
+
const v = value(inner, here);
|
|
2331
|
+
modes.set(mode, v.css);
|
|
2332
|
+
refs.push(...v.refs);
|
|
2333
|
+
}
|
|
2334
|
+
if (base === void 0) throw error("BAD_VALUE", at, "a value per mode needs a `base` value");
|
|
2335
|
+
} else {
|
|
2336
|
+
const v = value(node, at);
|
|
2337
|
+
base = v.css;
|
|
2338
|
+
refs.push(...v.refs);
|
|
2339
|
+
}
|
|
2340
|
+
found.set(token, {
|
|
2341
|
+
token,
|
|
2342
|
+
at,
|
|
2343
|
+
base,
|
|
2344
|
+
modes,
|
|
2345
|
+
refs
|
|
2346
|
+
});
|
|
2347
|
+
};
|
|
2348
|
+
const walk = (scale, tree, segments) => {
|
|
2349
|
+
for (const [key, node] of Object.entries(tree)) {
|
|
2350
|
+
if (node === void 0) continue;
|
|
2351
|
+
const names = key === "DEFAULT" ? segments : [...segments, key];
|
|
2352
|
+
const at = [
|
|
2353
|
+
scale,
|
|
2354
|
+
...segments,
|
|
2355
|
+
key
|
|
2356
|
+
].join(".");
|
|
2357
|
+
if (isPlainObject(node) && !Object.keys(node).some((k) => k.startsWith("_")) && !(Object.keys(node).length === 1 && Object.hasOwn(node, "base")) && key !== "DEFAULT") walk(scale, node, names);
|
|
2358
|
+
else override(scale, names.join("."), node, at);
|
|
2359
|
+
}
|
|
2360
|
+
};
|
|
2361
|
+
for (const [scale, tree] of Object.entries(overrides)) {
|
|
2362
|
+
if (tree === void 0) continue;
|
|
2363
|
+
if (!isScaleName(scale)) throw error("UNKNOWN_TOKEN", scale, "is not a token scale", didYouMean(closestNames(scale, SCALES)) ?? `Scales: ${SCALES.join(", ")}.`);
|
|
2364
|
+
if (scale === "breakpoints" || scale === "containers") throw error("BAD_VALUE", scale, "cannot be themed: media and container queries cannot read custom properties");
|
|
2365
|
+
if (!isPlainObject(tree)) throw error("BAD_VALUE", scale, "expected tokens by name");
|
|
2366
|
+
walk(scale, tree, []);
|
|
2367
|
+
}
|
|
2368
|
+
if (found.size === 0) throw error("BAD_CALL", "", "theme() needs at least one token to override");
|
|
2369
|
+
const overridden = [...found.values()].sort((a, b) => graph.order.get(a.token) - graph.order.get(b.token));
|
|
2370
|
+
const affected = dependentsOf(graph, overridden.map((o) => o.token));
|
|
2371
|
+
checkCycles(graph, found, affected, error);
|
|
2372
|
+
const normalized = overridden.map((o) => [
|
|
2373
|
+
`${o.token.scale}.${o.token.path}`,
|
|
2374
|
+
o.base,
|
|
2375
|
+
Object.fromEntries(themeModes(graph, o.token).flatMap((m) => {
|
|
2376
|
+
const v = o.modes.get(m.name);
|
|
2377
|
+
return v === void 0 ? [] : [[m.name, v]];
|
|
2378
|
+
}))
|
|
2379
|
+
]);
|
|
2380
|
+
const className = `${THEME_KEY}_${hash(stableStringify(normalized))}`;
|
|
2381
|
+
const self = `.${escapeClassName(className)}`;
|
|
2382
|
+
const selection = (token, fallback) => themeModes(graph, token).reduce((inner, mode) => `var(${selectedVar(token, mode.name)},${inner})`, fallback);
|
|
2383
|
+
const selected = (token) => themeModes(graph, token).map((mode) => `${selectedVar(token, mode.name)}:var(${modeFlag(system, mode.name)}) var(${themeModeVar(token, mode.name)})`);
|
|
2384
|
+
const own = [];
|
|
2385
|
+
for (const token of graph.tokens) {
|
|
2386
|
+
if (!affected.has(token)) continue;
|
|
2387
|
+
const o = found.get(token);
|
|
2388
|
+
if (o) {
|
|
2389
|
+
own.push(`${themeVar(token)}:${o.base}`);
|
|
2390
|
+
for (const mode of themeModes(graph, token)) own.push(`${themeModeVar(token, mode.name)}:${o.modes.get(mode.name) ?? "initial"}`);
|
|
2391
|
+
own.push(...selected(token), `${token.cssVar}:${selection(token, `var(${themeVar(token)})`)}`);
|
|
2392
|
+
continue;
|
|
2393
|
+
}
|
|
2394
|
+
for (const mode of graph.modes) {
|
|
2395
|
+
const v = token.modes?.[mode.name];
|
|
2396
|
+
if (v === void 0 || !referencedTokens(v, graph.byVar).some((r) => affected.has(r))) continue;
|
|
2397
|
+
own.push(`${modeVar(token.cssVar, mode.name)}:var(${modeFlag(system, mode.name)}) ${v}`);
|
|
2398
|
+
}
|
|
2399
|
+
own.push(`${token.cssVar}:${selection(token, `var(${themeVar(token)},${classicValue(token, graph.modes)})`)}`);
|
|
2400
|
+
}
|
|
2401
|
+
const switched = overridden.filter((o) => themeModes(graph, o.token).length > 0);
|
|
2402
|
+
const scopes = switched.length > 0 ? graph.scopes.map((scope) => isCompound(scope) ? `${self} ${scope}` : `${self} :is(${scope})`) : [];
|
|
2403
|
+
let css = `:where(${self}){${own.join(";")}}`;
|
|
2404
|
+
if (scopes.length > 0) {
|
|
2405
|
+
const inner = switched.flatMap((o) => [...selected(o.token), `${o.token.cssVar}:${selection(o.token, `var(${themeVar(o.token)})`)}`]);
|
|
2406
|
+
css += `:where(${scopes.join(",")}){${inner.join(";")}}`;
|
|
2407
|
+
}
|
|
2408
|
+
const tokens = /* @__PURE__ */ new Map();
|
|
2409
|
+
for (const o of overridden) for (const t of [o.token, ...o.refs]) tokens.set(`${t.scale}:${t.path}`, {
|
|
2410
|
+
scale: t.scale,
|
|
2411
|
+
path: t.path
|
|
2412
|
+
});
|
|
2413
|
+
return {
|
|
2414
|
+
id: className,
|
|
2415
|
+
kind: "theme",
|
|
2416
|
+
css: `@layer vicss.tokens{${css}}`,
|
|
2417
|
+
tokens: [...tokens.values()],
|
|
2418
|
+
overrides
|
|
2419
|
+
};
|
|
2420
|
+
}
|
|
2421
|
+
/** `roots` and every token that depends on one, transitively. */
|
|
2422
|
+
function dependentsOf(graph, roots) {
|
|
2423
|
+
const out = /* @__PURE__ */ new Set();
|
|
2424
|
+
const visit = (token) => {
|
|
2425
|
+
if (out.has(token)) return;
|
|
2426
|
+
out.add(token);
|
|
2427
|
+
for (const next of graph.dependents.get(token) ?? []) visit(next);
|
|
2428
|
+
};
|
|
2429
|
+
for (const root of roots) visit(root);
|
|
2430
|
+
return out;
|
|
2431
|
+
}
|
|
2432
|
+
/**
|
|
2433
|
+
* The theme's element declares an overridden token from its theme value and
|
|
2434
|
+
* every dependent from its own value: an override that references a
|
|
2435
|
+
* dependent of itself makes a cycle, and the browser would drop every
|
|
2436
|
+
* property on it.
|
|
2437
|
+
*/
|
|
2438
|
+
function checkCycles(graph, found, affected, error) {
|
|
2439
|
+
const next = (token) => {
|
|
2440
|
+
return (found.get(token)?.refs ?? [...graph.refs.get(token) ?? []]).filter((r) => affected.has(r));
|
|
2441
|
+
};
|
|
2442
|
+
const done = /* @__PURE__ */ new Set();
|
|
2443
|
+
const stack = [];
|
|
2444
|
+
const visit = (token) => {
|
|
2445
|
+
if (done.has(token)) return;
|
|
2446
|
+
const index = stack.indexOf(token);
|
|
2447
|
+
if (index !== -1) {
|
|
2448
|
+
const cycle = [...stack.slice(index), token];
|
|
2449
|
+
const start = cycle.find((t) => found.has(t));
|
|
2450
|
+
throw error("BAD_VALUE", found.get(start).at, `the theme value refers back to itself: ${cycle.map((t) => `${t.scale}.${t.path}`).join(" → ")}`);
|
|
2451
|
+
}
|
|
2452
|
+
stack.push(token);
|
|
2453
|
+
for (const target of next(token)) visit(target);
|
|
2454
|
+
stack.pop();
|
|
2455
|
+
done.add(token);
|
|
2456
|
+
};
|
|
2457
|
+
for (const token of found.keys()) visit(token);
|
|
2458
|
+
}
|
|
2459
|
+
//#endregion
|
|
2460
|
+
export { createLocator as C, opaqueAt as S, evaluate as _, buildRecipe as a, isIncomplete as b, resolveRecipe as c, baseRule as d, checkBases as f, blamedNode as g, NOT_HANDLED as h, RecipeValue as i, responsiveConditions as l, shadowedByEarlier as m, compileTheme as n, printTable as o, isBaseClass as p, analyzeScopes as r, recipeKey as s, THEME_KEY as t, baseAtoms as u, evaluateBinding as v, isOpaque as x, isDynamic as y };
|