@webto-id/variant-check 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/dist/cli.js +3628 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2374 -0
- package/package.json +49 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2374 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
4
|
+
|
|
5
|
+
// ../variant-compiler/src/ir.ts
|
|
6
|
+
var COMPONENT_NAMES = ["Container", "Button", "EditUrlPill", "ViewMoreLink"];
|
|
7
|
+
|
|
8
|
+
// ../variant-compiler/src/parse/parser.ts
|
|
9
|
+
var ParseError = class extends Error {
|
|
10
|
+
constructor(message, line, code = "parse") {
|
|
11
|
+
super(message);
|
|
12
|
+
this.line = line;
|
|
13
|
+
this.code = code;
|
|
14
|
+
}
|
|
15
|
+
static {
|
|
16
|
+
__name(this, "ParseError");
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var RAW_TEXT_TAGS = /* @__PURE__ */ new Set(["style", "script"]);
|
|
20
|
+
var VOID_TAGS = /* @__PURE__ */ new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "source", "track", "wbr"]);
|
|
21
|
+
var BIN_PREC = {
|
|
22
|
+
"??": 1,
|
|
23
|
+
"||": 2,
|
|
24
|
+
"&&": 3,
|
|
25
|
+
"===": 6,
|
|
26
|
+
"!==": 6,
|
|
27
|
+
"==": 6,
|
|
28
|
+
"!=": 6,
|
|
29
|
+
"<": 7,
|
|
30
|
+
">": 7,
|
|
31
|
+
"<=": 7,
|
|
32
|
+
">=": 7,
|
|
33
|
+
"+": 8,
|
|
34
|
+
"-": 8,
|
|
35
|
+
"*": 9,
|
|
36
|
+
"/": 9,
|
|
37
|
+
"%": 9
|
|
38
|
+
};
|
|
39
|
+
var MULTI_OPS = ["?.", "??", "||", "&&", "===", "!==", "==", "!=", "<=", ">=", "=>"];
|
|
40
|
+
var Parser = class {
|
|
41
|
+
constructor(src, lineOffset = 0) {
|
|
42
|
+
this.src = src;
|
|
43
|
+
this.lineOffset = lineOffset;
|
|
44
|
+
}
|
|
45
|
+
static {
|
|
46
|
+
__name(this, "Parser");
|
|
47
|
+
}
|
|
48
|
+
pos = 0;
|
|
49
|
+
styles = [];
|
|
50
|
+
scripts = [];
|
|
51
|
+
// ---------------------------------------------------------------- utils
|
|
52
|
+
lineAt(pos = this.pos) {
|
|
53
|
+
let line = 1;
|
|
54
|
+
for (let i = 0; i < pos && i < this.src.length; i++) if (this.src.charCodeAt(i) === 10) line++;
|
|
55
|
+
return line + this.lineOffset;
|
|
56
|
+
}
|
|
57
|
+
fail(msg, code = "parse") {
|
|
58
|
+
throw new ParseError(msg, this.lineAt(), code);
|
|
59
|
+
}
|
|
60
|
+
peek(n = 0) {
|
|
61
|
+
return this.src[this.pos + n] ?? "";
|
|
62
|
+
}
|
|
63
|
+
startsWith(s) {
|
|
64
|
+
return this.src.startsWith(s, this.pos);
|
|
65
|
+
}
|
|
66
|
+
eof() {
|
|
67
|
+
return this.pos >= this.src.length;
|
|
68
|
+
}
|
|
69
|
+
skipWs() {
|
|
70
|
+
for (; ; ) {
|
|
71
|
+
const c = this.peek();
|
|
72
|
+
if (c === " " || c === "\n" || c === " " || c === "\r") {
|
|
73
|
+
this.pos++;
|
|
74
|
+
} else if (this.startsWith("//")) {
|
|
75
|
+
while (!this.eof() && this.peek() !== "\n") this.pos++;
|
|
76
|
+
} else if (this.startsWith("/*")) {
|
|
77
|
+
const end = this.src.indexOf("*/", this.pos + 2);
|
|
78
|
+
if (end < 0) this.fail("unterminated comment");
|
|
79
|
+
this.pos = end + 2;
|
|
80
|
+
} else return;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// ----------------------------------------------------------- template
|
|
84
|
+
/** Parse a whole template (children until EOF). */
|
|
85
|
+
parseTemplate() {
|
|
86
|
+
const root = this.parseChildren(null);
|
|
87
|
+
if (!this.eof()) this.fail(`unexpected '${this.peek()}'`);
|
|
88
|
+
return { root, styles: this.styles, scripts: this.scripts };
|
|
89
|
+
}
|
|
90
|
+
parseChildren(closeTag) {
|
|
91
|
+
const out = [];
|
|
92
|
+
let text = "";
|
|
93
|
+
const flush = /* @__PURE__ */ __name(() => {
|
|
94
|
+
if (text) {
|
|
95
|
+
out.push({ t: "txt", v: text });
|
|
96
|
+
text = "";
|
|
97
|
+
}
|
|
98
|
+
}, "flush");
|
|
99
|
+
while (!this.eof()) {
|
|
100
|
+
const c = this.peek();
|
|
101
|
+
if (c === "<") {
|
|
102
|
+
if (this.startsWith("<!--")) {
|
|
103
|
+
const end = this.src.indexOf("-->", this.pos);
|
|
104
|
+
if (end < 0) this.fail("unterminated HTML comment");
|
|
105
|
+
this.pos = end + 3;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (this.startsWith("</")) {
|
|
109
|
+
flush();
|
|
110
|
+
const m = /^<\/\s*([A-Za-z][\w:.-]*)?\s*>/.exec(this.src.slice(this.pos));
|
|
111
|
+
if (!m) this.fail("malformed closing tag");
|
|
112
|
+
const name = m[1] ?? "";
|
|
113
|
+
if (closeTag === null) this.fail(`unexpected closing tag </${name}>`);
|
|
114
|
+
if (name !== closeTag) this.fail(`expected </${closeTag}> but found </${name}>`);
|
|
115
|
+
this.pos += m[0].length;
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
if (/[A-Za-z>]/.test(this.peek(1))) {
|
|
119
|
+
flush();
|
|
120
|
+
const nodes = this.parseElement();
|
|
121
|
+
out.push(...nodes);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
text += c;
|
|
125
|
+
this.pos++;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (c === "{") {
|
|
129
|
+
flush();
|
|
130
|
+
this.pos++;
|
|
131
|
+
this.skipWs();
|
|
132
|
+
if (this.peek() === "}") {
|
|
133
|
+
this.pos++;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const e = this.parseExpr();
|
|
137
|
+
this.skipWs();
|
|
138
|
+
if (this.peek() !== "}") this.fail("expected '}' after expression");
|
|
139
|
+
this.pos++;
|
|
140
|
+
out.push({ t: "ex", e });
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (c === "}") this.fail("unexpected '}' in template");
|
|
144
|
+
text += c;
|
|
145
|
+
this.pos++;
|
|
146
|
+
}
|
|
147
|
+
flush();
|
|
148
|
+
if (closeTag !== null) this.fail(`missing closing tag </${closeTag}>`);
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
/** Parses `<tag ...>…</tag>` at pos. Returns nodes (fragments flatten). */
|
|
152
|
+
parseElement() {
|
|
153
|
+
const startLine = this.lineAt();
|
|
154
|
+
if (!this.startsWith("<")) this.fail("expected '<'");
|
|
155
|
+
this.pos++;
|
|
156
|
+
if (this.peek() === ">") {
|
|
157
|
+
this.pos++;
|
|
158
|
+
return this.parseChildren("");
|
|
159
|
+
}
|
|
160
|
+
const m = /^[A-Za-z][\w:.-]*/.exec(this.src.slice(this.pos));
|
|
161
|
+
if (!m) this.fail("expected tag name");
|
|
162
|
+
const tag = m[0];
|
|
163
|
+
this.pos += tag.length;
|
|
164
|
+
const attrs = this.parseAttrs();
|
|
165
|
+
let selfClose = false;
|
|
166
|
+
this.skipWs();
|
|
167
|
+
if (this.startsWith("/>")) {
|
|
168
|
+
selfClose = true;
|
|
169
|
+
this.pos += 2;
|
|
170
|
+
} else if (this.peek() === ">") {
|
|
171
|
+
this.pos++;
|
|
172
|
+
} else this.fail(`malformed tag <${tag}>`);
|
|
173
|
+
const lower = tag.toLowerCase();
|
|
174
|
+
if (RAW_TEXT_TAGS.has(lower)) {
|
|
175
|
+
if (selfClose) this.fail(`<${tag}> cannot be self-closing`);
|
|
176
|
+
const close = new RegExp(`</${lower}\\s*>`, "i");
|
|
177
|
+
const rest = this.src.slice(this.pos);
|
|
178
|
+
const mm = close.exec(rest);
|
|
179
|
+
if (!mm) this.fail(`missing </${lower}>`);
|
|
180
|
+
const text = rest.slice(0, mm.index);
|
|
181
|
+
this.pos += mm.index + mm[0].length;
|
|
182
|
+
const raw = {};
|
|
183
|
+
for (const [k, v] of Object.entries(attrs)) raw[k] = typeof v === "string" || v === true ? v : "{expr}";
|
|
184
|
+
const block = { tag: lower, attrs: raw, text, line: startLine };
|
|
185
|
+
(lower === "style" ? this.styles : this.scripts).push(block);
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
const children = selfClose || VOID_TAGS.has(lower) ? [] : this.parseChildren(tag);
|
|
189
|
+
if (tag === "Fragment") return children;
|
|
190
|
+
if (/^[A-Z]/.test(tag)) {
|
|
191
|
+
if (!COMPONENT_NAMES.includes(tag)) {
|
|
192
|
+
throw new ParseError(`unknown component <${tag}>; allowed: ${COMPONENT_NAMES.join(", ")}`, startLine, "component");
|
|
193
|
+
}
|
|
194
|
+
return [{ t: "comp", name: tag, props: attrs, ch: children }];
|
|
195
|
+
}
|
|
196
|
+
return [{ t: "el", tag: lower, attrs, ch: children }];
|
|
197
|
+
}
|
|
198
|
+
parseAttrs() {
|
|
199
|
+
const attrs = {};
|
|
200
|
+
for (; ; ) {
|
|
201
|
+
this.skipWs();
|
|
202
|
+
const c = this.peek();
|
|
203
|
+
if (c === ">" || this.startsWith("/>") || this.eof()) return attrs;
|
|
204
|
+
if (c === "{") {
|
|
205
|
+
this.pos++;
|
|
206
|
+
this.skipWs();
|
|
207
|
+
if (this.startsWith("...")) this.fail("spread attributes are not allowed", "spread");
|
|
208
|
+
const m2 = /^[A-Za-z_$][\w$]*/.exec(this.src.slice(this.pos));
|
|
209
|
+
if (!m2) this.fail("bad shorthand attribute");
|
|
210
|
+
this.pos += m2[0].length;
|
|
211
|
+
this.skipWs();
|
|
212
|
+
if (this.peek() !== "}") this.fail("expected '}' after shorthand attribute");
|
|
213
|
+
this.pos++;
|
|
214
|
+
attrs[m2[0]] = { e: { k: "id", n: m2[0] } };
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const m = /^[^\s"'<>\/=`{}]+/.exec(this.src.slice(this.pos));
|
|
218
|
+
if (!m) this.fail(`bad attribute near '${this.src.slice(this.pos, this.pos + 12)}'`);
|
|
219
|
+
const name = m[0];
|
|
220
|
+
this.pos += name.length;
|
|
221
|
+
this.skipWs();
|
|
222
|
+
if (this.peek() !== "=") {
|
|
223
|
+
attrs[name] = true;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
this.pos++;
|
|
227
|
+
this.skipWs();
|
|
228
|
+
const q = this.peek();
|
|
229
|
+
if (q === '"' || q === "'") {
|
|
230
|
+
const end = this.src.indexOf(q, this.pos + 1);
|
|
231
|
+
if (end < 0) this.fail("unterminated attribute value");
|
|
232
|
+
attrs[name] = this.src.slice(this.pos + 1, end);
|
|
233
|
+
this.pos = end + 1;
|
|
234
|
+
} else if (q === "{") {
|
|
235
|
+
this.pos++;
|
|
236
|
+
this.skipWs();
|
|
237
|
+
const e = this.parseExpr();
|
|
238
|
+
this.skipWs();
|
|
239
|
+
if (this.peek() !== "}") this.fail(`expected '}' after attribute ${name}`);
|
|
240
|
+
this.pos++;
|
|
241
|
+
attrs[name] = name === "class:list" ? { list: e } : { e };
|
|
242
|
+
} else if (q === "`") {
|
|
243
|
+
const e = this.parseExpr();
|
|
244
|
+
attrs[name] = { e };
|
|
245
|
+
} else {
|
|
246
|
+
const v = /^[^\s>]+/.exec(this.src.slice(this.pos));
|
|
247
|
+
if (!v) this.fail("bad attribute value");
|
|
248
|
+
attrs[name] = v[0];
|
|
249
|
+
this.pos += v[0].length;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// ---------------------------------------------------------- expressions
|
|
254
|
+
parseExpr() {
|
|
255
|
+
return this.parseTernary();
|
|
256
|
+
}
|
|
257
|
+
peekOp() {
|
|
258
|
+
for (const op of MULTI_OPS) if (this.startsWith(op)) return op;
|
|
259
|
+
const c = this.peek();
|
|
260
|
+
if ("()[]{},.:?!-+*/%<>".includes(c) && c !== "") return c;
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
parseTernary() {
|
|
264
|
+
const t = this.parseBinary(1);
|
|
265
|
+
this.skipWs();
|
|
266
|
+
if (this.peek() === "?" && !this.startsWith("?.") && !this.startsWith("??")) {
|
|
267
|
+
this.pos++;
|
|
268
|
+
this.skipWs();
|
|
269
|
+
const a = this.parseTernary();
|
|
270
|
+
this.skipWs();
|
|
271
|
+
if (this.peek() !== ":") this.fail("expected ':' in conditional expression");
|
|
272
|
+
this.pos++;
|
|
273
|
+
this.skipWs();
|
|
274
|
+
const b = this.parseTernary();
|
|
275
|
+
return { k: "cond", t, a, b };
|
|
276
|
+
}
|
|
277
|
+
return t;
|
|
278
|
+
}
|
|
279
|
+
parseBinary(min) {
|
|
280
|
+
let l = this.parseUnary();
|
|
281
|
+
for (; ; ) {
|
|
282
|
+
this.skipWs();
|
|
283
|
+
const op = this.peekOp();
|
|
284
|
+
if (!op) return l;
|
|
285
|
+
const prec = BIN_PREC[op];
|
|
286
|
+
if (prec === void 0 || prec < min) return l;
|
|
287
|
+
this.pos += op.length;
|
|
288
|
+
this.skipWs();
|
|
289
|
+
const r = this.parseBinary(prec + 1);
|
|
290
|
+
l = { k: "bin", op, l, r };
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
parseUnary() {
|
|
294
|
+
this.skipWs();
|
|
295
|
+
const c = this.peek();
|
|
296
|
+
if (c === "!" && this.peek(1) !== "=") {
|
|
297
|
+
this.pos++;
|
|
298
|
+
return { k: "un", op: "!", a: this.parseUnary() };
|
|
299
|
+
}
|
|
300
|
+
if ((c === "-" || c === "+") && !/[0-9.]/.test(this.peek(1))) {
|
|
301
|
+
this.pos++;
|
|
302
|
+
return { k: "un", op: c, a: this.parseUnary() };
|
|
303
|
+
}
|
|
304
|
+
if (this.startsWith("typeof ") || this.startsWith("typeof(")) {
|
|
305
|
+
this.pos += 6;
|
|
306
|
+
return { k: "un", op: "typeof", a: this.parseUnary() };
|
|
307
|
+
}
|
|
308
|
+
return this.parsePostfix();
|
|
309
|
+
}
|
|
310
|
+
parsePostfix() {
|
|
311
|
+
let e = this.parsePrimary();
|
|
312
|
+
for (; ; ) {
|
|
313
|
+
const save = this.pos;
|
|
314
|
+
this.skipWs();
|
|
315
|
+
if (this.startsWith("?.")) {
|
|
316
|
+
this.pos += 2;
|
|
317
|
+
if (this.peek() === "(") {
|
|
318
|
+
this.pos++;
|
|
319
|
+
e = { k: "call", c: e, a: this.parseArgs(), opt: true };
|
|
320
|
+
} else if (this.peek() === "[") {
|
|
321
|
+
this.pos++;
|
|
322
|
+
const i = this.parseExpr();
|
|
323
|
+
this.expectChar("]");
|
|
324
|
+
e = { k: "idx", o: e, i, opt: true };
|
|
325
|
+
} else {
|
|
326
|
+
e = { k: "mem", o: e, p: this.parseIdent(), opt: true };
|
|
327
|
+
}
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (this.peek() === "." && !this.startsWith("...")) {
|
|
331
|
+
this.pos++;
|
|
332
|
+
this.skipWs();
|
|
333
|
+
e = { k: "mem", o: e, p: this.parseIdent() };
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (this.peek() === "[") {
|
|
337
|
+
this.pos++;
|
|
338
|
+
this.skipWs();
|
|
339
|
+
const i = this.parseExpr();
|
|
340
|
+
this.skipWs();
|
|
341
|
+
this.expectChar("]");
|
|
342
|
+
e = { k: "idx", o: e, i };
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (this.peek() === "(") {
|
|
346
|
+
this.pos++;
|
|
347
|
+
e = { k: "call", c: e, a: this.parseArgs() };
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
this.pos = save;
|
|
351
|
+
return e;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
expectChar(c) {
|
|
355
|
+
this.skipWs();
|
|
356
|
+
if (this.peek() !== c) this.fail(`expected '${c}'`);
|
|
357
|
+
this.pos++;
|
|
358
|
+
}
|
|
359
|
+
parseIdent() {
|
|
360
|
+
const m = /^[A-Za-z_$][\w$]*/.exec(this.src.slice(this.pos));
|
|
361
|
+
if (!m) this.fail("expected identifier");
|
|
362
|
+
this.pos += m[0].length;
|
|
363
|
+
return m[0];
|
|
364
|
+
}
|
|
365
|
+
parseArgs() {
|
|
366
|
+
const a = [];
|
|
367
|
+
for (; ; ) {
|
|
368
|
+
this.skipWs();
|
|
369
|
+
if (this.peek() === ")") {
|
|
370
|
+
this.pos++;
|
|
371
|
+
return a;
|
|
372
|
+
}
|
|
373
|
+
a.push(this.parseExpr());
|
|
374
|
+
this.skipWs();
|
|
375
|
+
if (this.peek() === ",") this.pos++;
|
|
376
|
+
else if (this.peek() !== ")") this.fail("expected ',' or ')' in arguments");
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
tryArrowParams() {
|
|
380
|
+
const save = this.pos;
|
|
381
|
+
this.pos++;
|
|
382
|
+
const params = [];
|
|
383
|
+
for (; ; ) {
|
|
384
|
+
this.skipWs();
|
|
385
|
+
if (this.peek() === ")") {
|
|
386
|
+
this.pos++;
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
const m = /^[A-Za-z_$][\w$]*/.exec(this.src.slice(this.pos));
|
|
390
|
+
if (!m) {
|
|
391
|
+
this.pos = save;
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
params.push(m[0]);
|
|
395
|
+
this.pos += m[0].length;
|
|
396
|
+
this.skipWs();
|
|
397
|
+
if (this.peek() === ",") this.pos++;
|
|
398
|
+
else if (this.peek() !== ")") {
|
|
399
|
+
this.pos = save;
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
this.skipWs();
|
|
404
|
+
if (this.startsWith("=>")) {
|
|
405
|
+
this.pos += 2;
|
|
406
|
+
return params;
|
|
407
|
+
}
|
|
408
|
+
this.pos = save;
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
parseArrowBody() {
|
|
412
|
+
this.skipWs();
|
|
413
|
+
if (this.peek() === "{") this.fail("arrow functions must be expression-bodied (no `{ }` block)", "arrow-block");
|
|
414
|
+
return this.parseExpr();
|
|
415
|
+
}
|
|
416
|
+
parsePrimary() {
|
|
417
|
+
this.skipWs();
|
|
418
|
+
const c = this.peek();
|
|
419
|
+
if (c === "") this.fail("unexpected end of expression");
|
|
420
|
+
if (c === "<" && /[A-Za-z>]/.test(this.peek(1))) {
|
|
421
|
+
return { k: "jsx", n: this.parseElement() };
|
|
422
|
+
}
|
|
423
|
+
if (c === "(") {
|
|
424
|
+
const params = this.tryArrowParams();
|
|
425
|
+
if (params) return { k: "fn", params, body: this.parseArrowBody() };
|
|
426
|
+
this.pos++;
|
|
427
|
+
this.skipWs();
|
|
428
|
+
const e = this.parseExpr();
|
|
429
|
+
this.skipWs();
|
|
430
|
+
if (this.peek() !== ")") this.fail("expected ')'");
|
|
431
|
+
this.pos++;
|
|
432
|
+
return e;
|
|
433
|
+
}
|
|
434
|
+
if (c === "[") {
|
|
435
|
+
this.pos++;
|
|
436
|
+
const items = [];
|
|
437
|
+
for (; ; ) {
|
|
438
|
+
this.skipWs();
|
|
439
|
+
if (this.peek() === "]") {
|
|
440
|
+
this.pos++;
|
|
441
|
+
return { k: "arr", items };
|
|
442
|
+
}
|
|
443
|
+
if (this.startsWith("...")) this.fail("spread is not allowed", "spread");
|
|
444
|
+
items.push(this.parseExpr());
|
|
445
|
+
this.skipWs();
|
|
446
|
+
if (this.peek() === ",") this.pos++;
|
|
447
|
+
else if (this.peek() !== "]") this.fail("expected ',' or ']' in array");
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (c === "{") {
|
|
451
|
+
this.pos++;
|
|
452
|
+
const props = [];
|
|
453
|
+
for (; ; ) {
|
|
454
|
+
this.skipWs();
|
|
455
|
+
if (this.peek() === "}") {
|
|
456
|
+
this.pos++;
|
|
457
|
+
return { k: "obj", props };
|
|
458
|
+
}
|
|
459
|
+
if (this.startsWith("...")) this.fail("spread is not allowed", "spread");
|
|
460
|
+
let key;
|
|
461
|
+
const q = this.peek();
|
|
462
|
+
if (q === '"' || q === "'") {
|
|
463
|
+
key = this.parseString();
|
|
464
|
+
} else if (q === "[") {
|
|
465
|
+
this.fail("computed object keys are not allowed");
|
|
466
|
+
} else {
|
|
467
|
+
key = this.parseIdent();
|
|
468
|
+
}
|
|
469
|
+
this.skipWs();
|
|
470
|
+
if (this.peek() === ":") {
|
|
471
|
+
this.pos++;
|
|
472
|
+
props.push({ key, v: this.parseExpr() });
|
|
473
|
+
} else {
|
|
474
|
+
props.push({ key, v: { k: "id", n: key } });
|
|
475
|
+
}
|
|
476
|
+
this.skipWs();
|
|
477
|
+
if (this.peek() === ",") this.pos++;
|
|
478
|
+
else if (this.peek() !== "}") this.fail("expected ',' or '}' in object");
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (c === '"' || c === "'") return { k: "lit", v: this.parseString() };
|
|
482
|
+
if (c === "`") return this.parseTemplateLiteral();
|
|
483
|
+
if (/[0-9]/.test(c) || c === "." && /[0-9]/.test(this.peek(1))) {
|
|
484
|
+
const m = /^(?:0[xX][0-9a-fA-F]+|\d*\.?\d+(?:[eE][+-]?\d+)?|\d+)/.exec(this.src.slice(this.pos));
|
|
485
|
+
if (!m) this.fail("bad number");
|
|
486
|
+
this.pos += m[0].length;
|
|
487
|
+
return { k: "lit", v: Number(m[0]) };
|
|
488
|
+
}
|
|
489
|
+
if (/[A-Za-z_$]/.test(c)) {
|
|
490
|
+
const id = this.parseIdent();
|
|
491
|
+
switch (id) {
|
|
492
|
+
case "true":
|
|
493
|
+
return { k: "lit", v: true };
|
|
494
|
+
case "false":
|
|
495
|
+
return { k: "lit", v: false };
|
|
496
|
+
case "null":
|
|
497
|
+
return { k: "lit", v: null };
|
|
498
|
+
case "undefined":
|
|
499
|
+
return { k: "lit", v: void 0 };
|
|
500
|
+
case "new":
|
|
501
|
+
case "class":
|
|
502
|
+
case "function":
|
|
503
|
+
case "await":
|
|
504
|
+
case "yield":
|
|
505
|
+
case "import":
|
|
506
|
+
case "this":
|
|
507
|
+
case "super":
|
|
508
|
+
case "delete":
|
|
509
|
+
case "void":
|
|
510
|
+
case "in":
|
|
511
|
+
case "instanceof":
|
|
512
|
+
this.fail(`'${id}' is not allowed in variant expressions`, "forbidden");
|
|
513
|
+
}
|
|
514
|
+
this.skipWs();
|
|
515
|
+
if (this.startsWith("=>")) {
|
|
516
|
+
this.pos += 2;
|
|
517
|
+
return { k: "fn", params: [id], body: this.parseArrowBody() };
|
|
518
|
+
}
|
|
519
|
+
return { k: "id", n: id };
|
|
520
|
+
}
|
|
521
|
+
this.fail(`unexpected '${c}' in expression`);
|
|
522
|
+
}
|
|
523
|
+
parseString() {
|
|
524
|
+
const q = this.peek();
|
|
525
|
+
let i = this.pos + 1;
|
|
526
|
+
let s = "";
|
|
527
|
+
while (i < this.src.length && this.src[i] !== q) {
|
|
528
|
+
let ch = this.src[i];
|
|
529
|
+
if (ch === "\\") {
|
|
530
|
+
i++;
|
|
531
|
+
const n = this.src[i];
|
|
532
|
+
ch = n === "n" ? "\n" : n === "t" ? " " : n;
|
|
533
|
+
}
|
|
534
|
+
if (ch === "\n") this.fail("unterminated string");
|
|
535
|
+
s += ch;
|
|
536
|
+
i++;
|
|
537
|
+
}
|
|
538
|
+
if (i >= this.src.length) this.fail("unterminated string");
|
|
539
|
+
this.pos = i + 1;
|
|
540
|
+
return s;
|
|
541
|
+
}
|
|
542
|
+
parseTemplateLiteral() {
|
|
543
|
+
this.pos++;
|
|
544
|
+
const parts = [];
|
|
545
|
+
let s = "";
|
|
546
|
+
while (!this.eof() && this.peek() !== "`") {
|
|
547
|
+
if (this.startsWith("${")) {
|
|
548
|
+
if (s) parts.push(s);
|
|
549
|
+
s = "";
|
|
550
|
+
this.pos += 2;
|
|
551
|
+
parts.push(this.parseExpr());
|
|
552
|
+
this.skipWs();
|
|
553
|
+
if (this.peek() !== "}") this.fail("expected '}' in template literal");
|
|
554
|
+
this.pos++;
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
let ch = this.peek();
|
|
558
|
+
if (ch === "\\") {
|
|
559
|
+
this.pos++;
|
|
560
|
+
const n = this.peek();
|
|
561
|
+
ch = n === "n" ? "\n" : n;
|
|
562
|
+
}
|
|
563
|
+
s += ch;
|
|
564
|
+
this.pos++;
|
|
565
|
+
}
|
|
566
|
+
if (this.eof()) this.fail("unterminated template literal");
|
|
567
|
+
this.pos++;
|
|
568
|
+
if (s) parts.push(s);
|
|
569
|
+
return { k: "tpl", parts };
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
function parseExpression(src, lineOffset = 0) {
|
|
573
|
+
const p = new Parser(src, lineOffset);
|
|
574
|
+
const e = p.parseExpr();
|
|
575
|
+
p.skipWs();
|
|
576
|
+
if (!p.eof()) p.fail(`unexpected '${p.peek()}' after expression`);
|
|
577
|
+
return e;
|
|
578
|
+
}
|
|
579
|
+
__name(parseExpression, "parseExpression");
|
|
580
|
+
function parseTemplate(src, lineOffset = 0) {
|
|
581
|
+
return new Parser(src, lineOffset).parseTemplate();
|
|
582
|
+
}
|
|
583
|
+
__name(parseTemplate, "parseTemplate");
|
|
584
|
+
|
|
585
|
+
// ../variant-compiler/src/parse/frontmatter.ts
|
|
586
|
+
var HELPER_BY_IMPORT = {
|
|
587
|
+
sanitizeUrl: "url",
|
|
588
|
+
withUnsplashWidth: "img",
|
|
589
|
+
sectionT: "sectionT",
|
|
590
|
+
t: "t",
|
|
591
|
+
img: "img",
|
|
592
|
+
url: "url",
|
|
593
|
+
Container: "Container",
|
|
594
|
+
Button: "Button",
|
|
595
|
+
EditUrlPill: "EditUrlPill",
|
|
596
|
+
ViewMoreLink: "ViewMoreLink"
|
|
597
|
+
};
|
|
598
|
+
var ALLOWED_SOURCE = /^(webto\/variant|(\.{1,2}\/)+(lib\/(sanitize|image|section-i18n)|(components\/)?(v2\/)?(ui\/)?(Container|Button|ViewMoreLink)\.astro|(components\/)?EditUrlPill\.astro|ui\/(Container|Button)\.astro|ViewMoreLink\.astro))$/;
|
|
599
|
+
function stripComments(src) {
|
|
600
|
+
return src.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, " ")).replace(/(^|[^:\\])\/\/[^\n]*/g, (m, pre) => pre + " ".repeat(m.length - pre.length));
|
|
601
|
+
}
|
|
602
|
+
__name(stripComments, "stripComments");
|
|
603
|
+
function lineOf(src, idx) {
|
|
604
|
+
let l = 1;
|
|
605
|
+
for (let i = 0; i < idx; i++) if (src.charCodeAt(i) === 10) l++;
|
|
606
|
+
return l;
|
|
607
|
+
}
|
|
608
|
+
__name(lineOf, "lineOf");
|
|
609
|
+
function matchBrace(src, open, o = "{", c = "}") {
|
|
610
|
+
let depth = 0;
|
|
611
|
+
let inStr = null;
|
|
612
|
+
for (let i = open; i < src.length; i++) {
|
|
613
|
+
const ch = src[i];
|
|
614
|
+
if (inStr) {
|
|
615
|
+
if (ch === "\\") i++;
|
|
616
|
+
else if (ch === inStr) inStr = null;
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
if (ch === '"' || ch === "'" || ch === "`") inStr = ch;
|
|
620
|
+
else if (ch === o) depth++;
|
|
621
|
+
else if (ch === c && --depth === 0) return i;
|
|
622
|
+
}
|
|
623
|
+
return -1;
|
|
624
|
+
}
|
|
625
|
+
__name(matchBrace, "matchBrace");
|
|
626
|
+
function parseType(t, line) {
|
|
627
|
+
t = t.trim().replace(/;$/, "").trim();
|
|
628
|
+
const parts = splitTop(t, "|").map((s) => s.trim()).filter((s) => s !== "undefined" && s !== "null");
|
|
629
|
+
if (parts.length > 1) {
|
|
630
|
+
if (parts.every((p) => /^["'][^"']*["']$/.test(p))) return { type: "string", enum: parts.map((p) => p.slice(1, -1)) };
|
|
631
|
+
throw new ParseError(`unsupported union type '${t}' \u2014 only string-literal unions are allowed`, line, "props-type");
|
|
632
|
+
}
|
|
633
|
+
const one = parts[0] ?? t;
|
|
634
|
+
if (/^["'][^"']*["']$/.test(one)) return { type: "string", enum: [one.slice(1, -1)] };
|
|
635
|
+
if (one === "string") return { type: "string" };
|
|
636
|
+
if (one === "number") return { type: "number" };
|
|
637
|
+
if (one === "boolean") return { type: "boolean" };
|
|
638
|
+
if (/^\(.*\)\[\]$/.test(one)) return { type: "array", items: parseType(one.slice(1, -3), line) };
|
|
639
|
+
if (/\[\]$/.test(one)) return { type: "array", items: parseType(one.slice(0, -2), line) };
|
|
640
|
+
const arr = /^(?:Array|ReadonlyArray)<([\s\S]*)>$/.exec(one);
|
|
641
|
+
if (arr) return { type: "array", items: parseType(arr[1], line) };
|
|
642
|
+
if (one.startsWith("{")) {
|
|
643
|
+
const end = matchBrace(one, 0);
|
|
644
|
+
if (end < 0) throw new ParseError("unbalanced object type", line, "props-type");
|
|
645
|
+
const body = one.slice(1, end);
|
|
646
|
+
const props = {};
|
|
647
|
+
const required = [];
|
|
648
|
+
for (const f of parseFields(body, line)) {
|
|
649
|
+
props[f.key] = f.schema;
|
|
650
|
+
if (!f.optional) required.push(f.key);
|
|
651
|
+
}
|
|
652
|
+
const o = { type: "object", properties: props };
|
|
653
|
+
if (required.length) o.required = required;
|
|
654
|
+
return o;
|
|
655
|
+
}
|
|
656
|
+
throw new ParseError(`unsupported prop type '${one}' \u2014 use string | number | boolean | string-literal union | Array<\u2026> | { \u2026 }`, line, "props-type");
|
|
657
|
+
}
|
|
658
|
+
__name(parseType, "parseType");
|
|
659
|
+
function splitTop(s, sep) {
|
|
660
|
+
const out = [];
|
|
661
|
+
let depth = 0;
|
|
662
|
+
let cur = "";
|
|
663
|
+
let inStr = null;
|
|
664
|
+
for (let i = 0; i < s.length; i++) {
|
|
665
|
+
const ch = s[i];
|
|
666
|
+
if (inStr) {
|
|
667
|
+
cur += ch;
|
|
668
|
+
if (ch === inStr) inStr = null;
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
if (ch === '"' || ch === "'") inStr = ch;
|
|
672
|
+
if ("{[(<".includes(ch)) depth++;
|
|
673
|
+
if ("}])>".includes(ch)) depth--;
|
|
674
|
+
if (ch === sep && depth === 0) {
|
|
675
|
+
out.push(cur);
|
|
676
|
+
cur = "";
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
cur += ch;
|
|
680
|
+
}
|
|
681
|
+
if (cur.trim()) out.push(cur);
|
|
682
|
+
return out;
|
|
683
|
+
}
|
|
684
|
+
__name(splitTop, "splitTop");
|
|
685
|
+
function parseFields(body, baseLine) {
|
|
686
|
+
const fields = [];
|
|
687
|
+
let depth = 0;
|
|
688
|
+
let cur = "";
|
|
689
|
+
let doc;
|
|
690
|
+
const flush = /* @__PURE__ */ __name(() => {
|
|
691
|
+
const s = cur.trim();
|
|
692
|
+
cur = "";
|
|
693
|
+
if (!s) return;
|
|
694
|
+
const m = /^(?:readonly\s+)?([A-Za-z_$][\w$]*)\s*(\?)?\s*:\s*([\s\S]+)$/.exec(s);
|
|
695
|
+
if (!m) throw new ParseError(`cannot parse prop '${s.slice(0, 40)}'`, baseLine, "props");
|
|
696
|
+
fields.push({ key: m[1], optional: !!m[2], schema: parseType(m[3], baseLine), description: doc });
|
|
697
|
+
doc = void 0;
|
|
698
|
+
}, "flush");
|
|
699
|
+
const clean = body.replace(/\/\*\*([\s\S]*?)\*\//g, (_m, d) => `${d.replace(/^\s*\*\s?/gm, "").trim().replace(/\n/g, " ")}`);
|
|
700
|
+
for (let i = 0; i < clean.length; i++) {
|
|
701
|
+
const ch = clean[i];
|
|
702
|
+
if (ch === "") {
|
|
703
|
+
const end = clean.indexOf("", i);
|
|
704
|
+
doc = clean.slice(i + 1, end);
|
|
705
|
+
i = end;
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
if ("{[(<".includes(ch)) depth++;
|
|
709
|
+
if ("}])>".includes(ch)) depth--;
|
|
710
|
+
if ((ch === ";" || ch === "\n" || ch === ",") && depth === 0) {
|
|
711
|
+
flush();
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
cur += ch;
|
|
715
|
+
}
|
|
716
|
+
flush();
|
|
717
|
+
return fields;
|
|
718
|
+
}
|
|
719
|
+
__name(parseFields, "parseFields");
|
|
720
|
+
function parseFrontmatter(raw, lineOffset = 0) {
|
|
721
|
+
const src = stripComments(raw);
|
|
722
|
+
const out = { props: [], fields: [], consts: [], aliases: {} };
|
|
723
|
+
let i = 0;
|
|
724
|
+
const L = /* @__PURE__ */ __name((idx) => lineOf(src, idx) + lineOffset, "L");
|
|
725
|
+
let sawProps = false;
|
|
726
|
+
while (i < src.length) {
|
|
727
|
+
const ws = /^[\s;]+/.exec(src.slice(i));
|
|
728
|
+
if (ws) i += ws[0].length;
|
|
729
|
+
if (i >= src.length) break;
|
|
730
|
+
const rest = src.slice(i);
|
|
731
|
+
const line = L(i);
|
|
732
|
+
let m = /^import\s+(type\s+)?(?:([A-Za-z_$][\w$]*)|\{([^}]*)\}|\*\s+as\s+\w+)\s+from\s+["']([^"']+)["']\s*;?/.exec(rest);
|
|
733
|
+
if (m) {
|
|
734
|
+
const [, isType, def, named, source] = m;
|
|
735
|
+
i += m[0].length;
|
|
736
|
+
if (isType) continue;
|
|
737
|
+
if (!ALLOWED_SOURCE.test(source)) throw new ParseError(`import from '${source}' is not allowed \u2014 only "webto/variant" helpers (t, img, url, Container, Button, EditUrlPill, ViewMoreLink)`, line, "import");
|
|
738
|
+
const names = [];
|
|
739
|
+
if (def) names.push({ local: def, imported: def });
|
|
740
|
+
if (named) {
|
|
741
|
+
for (const part of named.split(",")) {
|
|
742
|
+
const p = part.trim();
|
|
743
|
+
if (!p || p.startsWith("type ")) continue;
|
|
744
|
+
const as = /^([A-Za-z_$][\w$]*)\s+as\s+([A-Za-z_$][\w$]*)$/.exec(p);
|
|
745
|
+
if (as) names.push({ local: as[2], imported: as[1] });
|
|
746
|
+
else names.push({ local: p, imported: p });
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
for (const n of names) {
|
|
750
|
+
const canon = HELPER_BY_IMPORT[n.imported];
|
|
751
|
+
if (!canon) throw new ParseError(`'${n.imported}' is not an available helper`, line, "import");
|
|
752
|
+
out.aliases[n.local] = canon;
|
|
753
|
+
}
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
if (/^import\s/.test(rest)) throw new ParseError("unsupported import form", line, "import");
|
|
757
|
+
m = /^(?:export\s+)?interface\s+Props\s*(?:extends[^{]*)?\{/.exec(rest);
|
|
758
|
+
if (m) {
|
|
759
|
+
if (/extends/.test(m[0])) throw new ParseError("`interface Props extends \u2026` is not supported", line, "props");
|
|
760
|
+
const open = i + m[0].length - 1;
|
|
761
|
+
const close = matchBrace(src, open);
|
|
762
|
+
if (close < 0) throw new ParseError("unbalanced interface Props", line, "props");
|
|
763
|
+
out.fields = parseFields(src.slice(open + 1, close), line);
|
|
764
|
+
sawProps = true;
|
|
765
|
+
i = close + 1;
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
m = /^(?:export\s+)?type\s+Props\s*=\s*\{/.exec(rest);
|
|
769
|
+
if (m) {
|
|
770
|
+
const open = i + m[0].length - 1;
|
|
771
|
+
const close = matchBrace(src, open);
|
|
772
|
+
if (close < 0) throw new ParseError("unbalanced type Props", line, "props");
|
|
773
|
+
out.fields = parseFields(src.slice(open + 1, close), line);
|
|
774
|
+
sawProps = true;
|
|
775
|
+
i = close + 1;
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
if (/^(?:export\s+)?(interface|type)\s/.test(rest)) {
|
|
779
|
+
const braceRel = rest.indexOf("{");
|
|
780
|
+
const semiRel = rest.indexOf(";");
|
|
781
|
+
if (braceRel >= 0 && (semiRel < 0 || braceRel < semiRel)) {
|
|
782
|
+
const close = matchBrace(src, i + braceRel);
|
|
783
|
+
if (close < 0) throw new ParseError("unbalanced type declaration", line, "types");
|
|
784
|
+
i = close + 1;
|
|
785
|
+
} else {
|
|
786
|
+
i += semiRel < 0 ? rest.length : semiRel + 1;
|
|
787
|
+
}
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
m = /^const\s*\{/.exec(rest);
|
|
791
|
+
if (m) {
|
|
792
|
+
const open = i + m[0].length - 1;
|
|
793
|
+
const close = matchBrace(src, open);
|
|
794
|
+
if (close < 0) throw new ParseError("unbalanced destructuring", line, "props");
|
|
795
|
+
const after = /^\s*=\s*Astro\.props(?:\s+as\s+[\w<>\[\]{}|\s,:;"']+?)?\s*;?/.exec(src.slice(close + 1));
|
|
796
|
+
if (!after) throw new ParseError("destructuring is only allowed from `Astro.props`", line, "props");
|
|
797
|
+
const body = src.slice(open + 1, close);
|
|
798
|
+
for (const entry of splitTop(body, ",")) {
|
|
799
|
+
const s = entry.trim();
|
|
800
|
+
if (!s) continue;
|
|
801
|
+
if (s.startsWith("...")) throw new ParseError("rest element in props destructuring is not allowed", line, "props");
|
|
802
|
+
const mm = /^([A-Za-z_$][\w$]*)\s*(?::\s*([A-Za-z_$][\w$]*))?\s*(?:=\s*([\s\S]+))?$/.exec(s);
|
|
803
|
+
if (!mm) throw new ParseError(`cannot parse destructured prop '${s.slice(0, 40)}'`, line, "props");
|
|
804
|
+
const key = mm[1];
|
|
805
|
+
const name = mm[2] ?? key;
|
|
806
|
+
const decl = { name, key };
|
|
807
|
+
if (mm[3] !== void 0) decl.def = parseExpression(mm[3], line - 1);
|
|
808
|
+
out.props.push(decl);
|
|
809
|
+
}
|
|
810
|
+
i = close + 1 + after[0].length;
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
m = /^const\s+([A-Za-z_$][\w$]*)\s*(?::\s*[^=]+?)?\s*=\s*/.exec(rest);
|
|
814
|
+
if (m) {
|
|
815
|
+
const name = m[1];
|
|
816
|
+
let j = i + m[0].length;
|
|
817
|
+
let depth = 0;
|
|
818
|
+
let inStr = null;
|
|
819
|
+
let k = j;
|
|
820
|
+
for (; k < src.length; k++) {
|
|
821
|
+
const ch = src[k];
|
|
822
|
+
if (inStr) {
|
|
823
|
+
if (ch === "\\") k++;
|
|
824
|
+
else if (ch === inStr) inStr = null;
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
if (ch === '"' || ch === "'" || ch === "`") inStr = ch;
|
|
828
|
+
else if ("{[(".includes(ch)) depth++;
|
|
829
|
+
else if ("}])".includes(ch)) depth--;
|
|
830
|
+
else if (ch === ";" && depth === 0) break;
|
|
831
|
+
else if (ch === "\n" && depth === 0) {
|
|
832
|
+
const next = src.slice(k + 1).replace(/^\s+/, "");
|
|
833
|
+
if (/^(const|let|var|import|interface|type|export|function|class)\b/.test(next) || next === "") break;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
const exprSrc = src.slice(j, k).trim();
|
|
837
|
+
i = k + 1;
|
|
838
|
+
if (/^sectionT\s*\(\s*Astro\.locals\s*\)$/.test(exprSrc) || /^t\s*\(\s*Astro\.locals\s*\)$/.test(exprSrc)) {
|
|
839
|
+
out.aliases[name] = "t";
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
if (/Astro\.locals[\s\S]*editChrome\s*===\s*true$/.test(exprSrc)) {
|
|
843
|
+
out.aliases[name] = "editChrome";
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
if (/\bAstro\./.test(exprSrc)) throw new ParseError("`Astro.*` is not available in variants (only `Astro.props` destructuring and `sectionT(Astro.locals)`)", line, "astro");
|
|
847
|
+
if (/\bas\s+(const|[A-Z][\w<>]*)\b/.test(exprSrc)) throw new ParseError("TypeScript `as` casts are not supported \u2014 remove the cast", line, "ts-cast");
|
|
848
|
+
out.consts.push({ name, e: parseExpression(exprSrc, line - 1) });
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
if (/^(let|var)\s/.test(rest)) throw new ParseError("only `const` declarations are allowed", line, "statement");
|
|
852
|
+
if (/^export\s/.test(rest)) throw new ParseError("`export` is not allowed in variants", line, "statement");
|
|
853
|
+
if (/^(function|class|if|for|while|switch|try|return|await)\b/.test(rest)) throw new ParseError("statements are not allowed in variants \u2014 use expressions in `const`", line, "statement");
|
|
854
|
+
throw new ParseError(`unexpected frontmatter code near '${rest.slice(0, 40).replace(/\n/g, " ")}'`, line, "statement");
|
|
855
|
+
}
|
|
856
|
+
if (!sawProps) throw new ParseError("`interface Props { \u2026 }` is required", 1 + lineOffset, "props");
|
|
857
|
+
return out;
|
|
858
|
+
}
|
|
859
|
+
__name(parseFrontmatter, "parseFrontmatter");
|
|
860
|
+
|
|
861
|
+
// ../variant-compiler/src/runtime/helpers.ts
|
|
862
|
+
function sanitizeUrl(url) {
|
|
863
|
+
if (!url) return "#";
|
|
864
|
+
const trimmed = url.trim();
|
|
865
|
+
if (!trimmed) return "#";
|
|
866
|
+
if (trimmed.startsWith("/") || trimmed.startsWith("#") || trimmed.startsWith("http://") || trimmed.startsWith("https://") || trimmed.startsWith("mailto:") || trimmed.startsWith("tel:")) {
|
|
867
|
+
return trimmed;
|
|
868
|
+
}
|
|
869
|
+
const lower = trimmed.toLowerCase().replace(/[\s-]/g, "");
|
|
870
|
+
if (lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:")) {
|
|
871
|
+
return "#";
|
|
872
|
+
}
|
|
873
|
+
return trimmed;
|
|
874
|
+
}
|
|
875
|
+
__name(sanitizeUrl, "sanitizeUrl");
|
|
876
|
+
function withUnsplashWidth(url, width) {
|
|
877
|
+
if (!url) return void 0;
|
|
878
|
+
if (url.startsWith("https://images.unsplash.com/")) {
|
|
879
|
+
if (/[?&]w=\d+/.test(url)) return url.replace(/([?&])w=\d+/, `$1w=${width}`);
|
|
880
|
+
const sep = url.includes("?") ? "&" : "?";
|
|
881
|
+
return `${url}${sep}w=${width}&auto=format`;
|
|
882
|
+
}
|
|
883
|
+
if (url.startsWith("https://images.pexels.com/")) {
|
|
884
|
+
let next = url;
|
|
885
|
+
const hadW = /[?&]w=\d+/.test(next);
|
|
886
|
+
const hadH = /[?&]h=\d+/.test(next);
|
|
887
|
+
const hadDpr = /[?&]dpr=\d+/.test(next);
|
|
888
|
+
if (hadW) next = next.replace(/([?&])w=\d+/, `$1w=${width}`);
|
|
889
|
+
if (hadH) next = next.replace(/([?&])h=\d+/, `$1h=${width}`);
|
|
890
|
+
if (hadDpr) next = next.replace(/([?&])dpr=\d+/, "$1dpr=1");
|
|
891
|
+
if (!hadW) {
|
|
892
|
+
const sep = next.includes("?") ? "&" : "?";
|
|
893
|
+
next = `${next}${sep}w=${width}`;
|
|
894
|
+
}
|
|
895
|
+
return next;
|
|
896
|
+
}
|
|
897
|
+
return url;
|
|
898
|
+
}
|
|
899
|
+
__name(withUnsplashWidth, "withUnsplashWidth");
|
|
900
|
+
function escText(s) {
|
|
901
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
902
|
+
}
|
|
903
|
+
__name(escText, "escText");
|
|
904
|
+
function escAttr(s) {
|
|
905
|
+
return s.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
906
|
+
}
|
|
907
|
+
__name(escAttr, "escAttr");
|
|
908
|
+
|
|
909
|
+
// ../variant-compiler/src/lint/html.ts
|
|
910
|
+
var ALLOWED_TAGS = /* @__PURE__ */ new Set([
|
|
911
|
+
"a",
|
|
912
|
+
"abbr",
|
|
913
|
+
"address",
|
|
914
|
+
"article",
|
|
915
|
+
"aside",
|
|
916
|
+
"b",
|
|
917
|
+
"bdi",
|
|
918
|
+
"bdo",
|
|
919
|
+
"blockquote",
|
|
920
|
+
"br",
|
|
921
|
+
"button",
|
|
922
|
+
"caption",
|
|
923
|
+
"cite",
|
|
924
|
+
"code",
|
|
925
|
+
"col",
|
|
926
|
+
"colgroup",
|
|
927
|
+
"data",
|
|
928
|
+
"dd",
|
|
929
|
+
"del",
|
|
930
|
+
"details",
|
|
931
|
+
"dfn",
|
|
932
|
+
"div",
|
|
933
|
+
"dl",
|
|
934
|
+
"dt",
|
|
935
|
+
"em",
|
|
936
|
+
"figcaption",
|
|
937
|
+
"figure",
|
|
938
|
+
"footer",
|
|
939
|
+
"h1",
|
|
940
|
+
"h2",
|
|
941
|
+
"h3",
|
|
942
|
+
"h4",
|
|
943
|
+
"h5",
|
|
944
|
+
"h6",
|
|
945
|
+
"header",
|
|
946
|
+
"hr",
|
|
947
|
+
"i",
|
|
948
|
+
"img",
|
|
949
|
+
"ins",
|
|
950
|
+
"kbd",
|
|
951
|
+
"label",
|
|
952
|
+
"li",
|
|
953
|
+
"main",
|
|
954
|
+
"mark",
|
|
955
|
+
"nav",
|
|
956
|
+
"ol",
|
|
957
|
+
"p",
|
|
958
|
+
"picture",
|
|
959
|
+
"pre",
|
|
960
|
+
"q",
|
|
961
|
+
"s",
|
|
962
|
+
"samp",
|
|
963
|
+
"section",
|
|
964
|
+
"small",
|
|
965
|
+
"source",
|
|
966
|
+
"span",
|
|
967
|
+
"strong",
|
|
968
|
+
"sub",
|
|
969
|
+
"summary",
|
|
970
|
+
"sup",
|
|
971
|
+
"table",
|
|
972
|
+
"tbody",
|
|
973
|
+
"td",
|
|
974
|
+
"tfoot",
|
|
975
|
+
"th",
|
|
976
|
+
"thead",
|
|
977
|
+
"time",
|
|
978
|
+
"tr",
|
|
979
|
+
"u",
|
|
980
|
+
"ul",
|
|
981
|
+
"var",
|
|
982
|
+
"video",
|
|
983
|
+
"audio",
|
|
984
|
+
"wbr",
|
|
985
|
+
"track",
|
|
986
|
+
// svg
|
|
987
|
+
"svg",
|
|
988
|
+
"path",
|
|
989
|
+
"circle",
|
|
990
|
+
"rect",
|
|
991
|
+
"line",
|
|
992
|
+
"polyline",
|
|
993
|
+
"polygon",
|
|
994
|
+
"g",
|
|
995
|
+
"defs",
|
|
996
|
+
"lineargradient",
|
|
997
|
+
"radialgradient",
|
|
998
|
+
"stop",
|
|
999
|
+
"clippath",
|
|
1000
|
+
"mask",
|
|
1001
|
+
"text",
|
|
1002
|
+
"tspan",
|
|
1003
|
+
"ellipse",
|
|
1004
|
+
"symbol",
|
|
1005
|
+
"pattern",
|
|
1006
|
+
"filter",
|
|
1007
|
+
"fegaussianblur",
|
|
1008
|
+
"feoffset",
|
|
1009
|
+
"feblend",
|
|
1010
|
+
"fecolormatrix",
|
|
1011
|
+
"femerge",
|
|
1012
|
+
"femergenode",
|
|
1013
|
+
"title",
|
|
1014
|
+
"desc"
|
|
1015
|
+
]);
|
|
1016
|
+
var ALLOWED_ATTR = /^(class|class:list|id|style|title|lang|dir|role|tabindex|hidden|href|target|rel|download|src|srcset|sizes|alt|width|height|loading|decoding|fetchpriority|type|open|datetime|cite|start|reversed|value|colspan|rowspan|scope|headers|for|controls|autoplay|muted|loop|playsinline|poster|preload|kind|srclang|label|default|media|translate|draggable|inputmode|itemprop|itemscope|itemtype|viewbox|xmlns|fill|stroke|stroke-width|stroke-linecap|stroke-linejoin|stroke-dasharray|stroke-dashoffset|d|cx|cy|r|rx|ry|x|y|x1|y1|x2|y2|points|transform|opacity|fill-opacity|stroke-opacity|fill-rule|clip-rule|clip-path|gradientunits|offset|stop-color|stop-opacity|patternunits|filter|stddeviation|in|in2|result|dx|dy|mode|values|text-anchor|font-size|font-weight|dominant-baseline|preserveaspectratio|vector-effect|shape-rendering|mask|font-family|letter-spacing)$/i;
|
|
1017
|
+
function push(ctx, level, code, message) {
|
|
1018
|
+
ctx.lint.push({ level, code, message });
|
|
1019
|
+
}
|
|
1020
|
+
__name(push, "push");
|
|
1021
|
+
function staticValue(v) {
|
|
1022
|
+
return typeof v === "string" ? v : null;
|
|
1023
|
+
}
|
|
1024
|
+
__name(staticValue, "staticValue");
|
|
1025
|
+
function hasEditableDescendant(nodes) {
|
|
1026
|
+
for (const n of nodes) {
|
|
1027
|
+
if (n.t === "el") {
|
|
1028
|
+
if ("data-edit-field" in n.attrs || "data-edit-url" in n.attrs) return true;
|
|
1029
|
+
if (hasEditableDescendant(n.ch)) return true;
|
|
1030
|
+
} else if (n.t === "comp") {
|
|
1031
|
+
if (n.name === "EditUrlPill") return true;
|
|
1032
|
+
if (hasEditableDescendant(n.ch)) return true;
|
|
1033
|
+
} else if (n.t === "ex") {
|
|
1034
|
+
if (exprHasJsx(n.e, hasEditableDescendant)) return true;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
return false;
|
|
1038
|
+
}
|
|
1039
|
+
__name(hasEditableDescendant, "hasEditableDescendant");
|
|
1040
|
+
function exprHasJsx(e, pred) {
|
|
1041
|
+
switch (e.k) {
|
|
1042
|
+
case "jsx":
|
|
1043
|
+
return pred(e.n);
|
|
1044
|
+
case "mem":
|
|
1045
|
+
return exprHasJsx(e.o, pred);
|
|
1046
|
+
case "idx":
|
|
1047
|
+
return exprHasJsx(e.o, pred) || exprHasJsx(e.i, pred);
|
|
1048
|
+
case "call":
|
|
1049
|
+
return exprHasJsx(e.c, pred) || e.a.some((a) => exprHasJsx(a, pred));
|
|
1050
|
+
case "un":
|
|
1051
|
+
return exprHasJsx(e.a, pred);
|
|
1052
|
+
case "bin":
|
|
1053
|
+
return exprHasJsx(e.l, pred) || exprHasJsx(e.r, pred);
|
|
1054
|
+
case "cond":
|
|
1055
|
+
return exprHasJsx(e.t, pred) || exprHasJsx(e.a, pred) || exprHasJsx(e.b, pred);
|
|
1056
|
+
case "tpl":
|
|
1057
|
+
return e.parts.some((p) => typeof p !== "string" && exprHasJsx(p, pred));
|
|
1058
|
+
case "arr":
|
|
1059
|
+
return e.items.some((a) => exprHasJsx(a, pred));
|
|
1060
|
+
case "obj":
|
|
1061
|
+
return e.props.some((p) => exprHasJsx(p.v, pred));
|
|
1062
|
+
case "fn":
|
|
1063
|
+
return exprHasJsx(e.body, pred);
|
|
1064
|
+
default:
|
|
1065
|
+
return false;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
__name(exprHasJsx, "exprHasJsx");
|
|
1069
|
+
function walkExpr(e, visit) {
|
|
1070
|
+
switch (e.k) {
|
|
1071
|
+
case "jsx":
|
|
1072
|
+
visit(e.n);
|
|
1073
|
+
break;
|
|
1074
|
+
case "mem":
|
|
1075
|
+
walkExpr(e.o, visit);
|
|
1076
|
+
break;
|
|
1077
|
+
case "idx":
|
|
1078
|
+
walkExpr(e.o, visit);
|
|
1079
|
+
walkExpr(e.i, visit);
|
|
1080
|
+
break;
|
|
1081
|
+
case "call":
|
|
1082
|
+
walkExpr(e.c, visit);
|
|
1083
|
+
e.a.forEach((a) => walkExpr(a, visit));
|
|
1084
|
+
break;
|
|
1085
|
+
case "un":
|
|
1086
|
+
walkExpr(e.a, visit);
|
|
1087
|
+
break;
|
|
1088
|
+
case "bin":
|
|
1089
|
+
walkExpr(e.l, visit);
|
|
1090
|
+
walkExpr(e.r, visit);
|
|
1091
|
+
break;
|
|
1092
|
+
case "cond":
|
|
1093
|
+
walkExpr(e.t, visit);
|
|
1094
|
+
walkExpr(e.a, visit);
|
|
1095
|
+
walkExpr(e.b, visit);
|
|
1096
|
+
break;
|
|
1097
|
+
case "tpl":
|
|
1098
|
+
e.parts.forEach((p) => typeof p !== "string" && walkExpr(p, visit));
|
|
1099
|
+
break;
|
|
1100
|
+
case "arr":
|
|
1101
|
+
e.items.forEach((a) => walkExpr(a, visit));
|
|
1102
|
+
break;
|
|
1103
|
+
case "obj":
|
|
1104
|
+
e.props.forEach((p) => walkExpr(p.v, visit));
|
|
1105
|
+
break;
|
|
1106
|
+
case "fn":
|
|
1107
|
+
walkExpr(e.body, visit);
|
|
1108
|
+
break;
|
|
1109
|
+
default:
|
|
1110
|
+
break;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
__name(walkExpr, "walkExpr");
|
|
1114
|
+
function lintNodes(nodes, ctx, ariaHidden = false) {
|
|
1115
|
+
for (const n of nodes) {
|
|
1116
|
+
if (n.t === "txt") {
|
|
1117
|
+
const s = n.v.replace(/\s+/g, " ").trim();
|
|
1118
|
+
if (s && /[A-Za-z]{2,}/.test(s) && !ariaHidden && ctx.inSvg === 0) {
|
|
1119
|
+
push(ctx, "warning", "dead-text", `literal text "${s.slice(0, 40)}" is not editable \u2014 use a content field with data-edit-field or a t() label`);
|
|
1120
|
+
}
|
|
1121
|
+
continue;
|
|
1122
|
+
}
|
|
1123
|
+
if (n.t === "ex") {
|
|
1124
|
+
walkExpr(n.e, (inner) => lintNodes(inner, ctx, ariaHidden));
|
|
1125
|
+
continue;
|
|
1126
|
+
}
|
|
1127
|
+
if (n.t === "comp") {
|
|
1128
|
+
for (const [k, v] of Object.entries(n.props)) {
|
|
1129
|
+
const lower = k.toLowerCase();
|
|
1130
|
+
if (/^on[a-z]/.test(lower) || lower.startsWith("set:") || lower === "srcdoc") push(ctx, "error", "attr", `prop '${k}' on <${n.name}> is not allowed`);
|
|
1131
|
+
if (lower === "href" || lower === "url") checkUrl(k, v, n.name, ctx);
|
|
1132
|
+
}
|
|
1133
|
+
if (n.name === "Button") ctx.inButton++;
|
|
1134
|
+
lintNodes(n.ch, ctx, ariaHidden);
|
|
1135
|
+
if (n.name === "Button") ctx.inButton--;
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
const tag = n.tag;
|
|
1139
|
+
if (!ALLOWED_TAGS.has(tag)) {
|
|
1140
|
+
push(ctx, "error", "tag", `<${tag}> is not allowed`);
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
const hidden = ariaHidden || staticValue(n.attrs["aria-hidden"]) === "true";
|
|
1144
|
+
for (const [k, v] of Object.entries(n.attrs)) lintAttr(k, v, tag, ctx);
|
|
1145
|
+
if (tag === "img" && !("alt" in n.attrs)) push(ctx, "warning", "img-alt", "<img> without alt");
|
|
1146
|
+
if (tag === "a") {
|
|
1147
|
+
const target = staticValue(n.attrs.target);
|
|
1148
|
+
if (target === "_blank" && !("rel" in n.attrs)) n.attrs.rel = "noopener";
|
|
1149
|
+
if (ctx.inButton === 0 && "href" in n.attrs && !("data-track" in n.attrs) && hasEditableDescendant(n.ch)) {
|
|
1150
|
+
n.attrs["data-track"] = "cta";
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
if ("id" in n.attrs) push(ctx, "warning", "id-attr", `<${tag} id> \u2014 the editor's click walker stops at ids; prefer a data-* attribute`);
|
|
1154
|
+
if (tag === "svg") ctx.inSvg++;
|
|
1155
|
+
lintNodes(n.ch, ctx, hidden);
|
|
1156
|
+
if (tag === "svg") ctx.inSvg--;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
__name(lintNodes, "lintNodes");
|
|
1160
|
+
function lintAttr(name, v, tag, ctx) {
|
|
1161
|
+
const lower = name.toLowerCase();
|
|
1162
|
+
if (/^on[a-z]/.test(lower)) {
|
|
1163
|
+
push(ctx, "error", "attr-handler", `inline event handler '${name}' on <${tag}> is not allowed \u2014 use a <script is:inline> block`);
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
if (lower === "srcdoc" || lower.startsWith("set:") || lower === "is:raw" || lower === "define:vars" || lower === "formaction") {
|
|
1167
|
+
push(ctx, "error", "attr", `attribute '${name}' is not allowed`);
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
if (lower.startsWith("data-") || lower.startsWith("aria-") || lower.startsWith("xlink:") || lower === "class:list") {
|
|
1171
|
+
if (lower === "xlink:href" || lower === "data-lightbox-src") checkUrl(name, v, tag, ctx);
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
if (!ALLOWED_ATTR.test(lower)) {
|
|
1175
|
+
push(ctx, "error", "attr", `attribute '${name}' on <${tag}> is not allowed`);
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
if (lower === "href" || lower === "src" || lower === "poster") checkUrl(name, v, tag, ctx);
|
|
1179
|
+
if (lower === "style") {
|
|
1180
|
+
const s = staticValue(v);
|
|
1181
|
+
if (s !== null) {
|
|
1182
|
+
if (/url\s*\(|expression\s*\(|@import|behavior\s*:/i.test(s)) push(ctx, "error", "style-url", `inline style on <${tag}> must not use url()/expression()`);
|
|
1183
|
+
if (/position\s*:\s*fixed/i.test(s) && ctx.sectionType !== "navbar" && ctx.sectionType !== "banner") push(ctx, "error", "fixed", `position: fixed is only allowed in navbar/banner variants`);
|
|
1184
|
+
if (/#[0-9a-f]{3,8}\b/i.test(s) || /\b(rgb|hsl)a?\(/i.test(s)) push(ctx, "warning", "hardcoded-color", `inline style on <${tag}> hardcodes a color \u2014 use var(--color-*)`);
|
|
1185
|
+
if (/border-radius\s*:\s*\d/i.test(s)) push(ctx, "warning", "hardcoded-radius", `inline style on <${tag}> hardcodes border-radius \u2014 use var(--radius)`);
|
|
1186
|
+
if (/font-family\s*:(?![^;]*var\(--font-)/i.test(s)) push(ctx, "warning", "hardcoded-font", `inline style on <${tag}> hardcodes font-family \u2014 use var(--font-heading|--font-body)`);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
if (lower === "target" && staticValue(v) === "_top") push(ctx, "error", "attr", "target=_top is not allowed");
|
|
1190
|
+
if (lower === "rel" && /import|preload|prefetch/i.test(staticValue(v) ?? "")) push(ctx, "error", "attr", "rel=preload/import is not allowed");
|
|
1191
|
+
}
|
|
1192
|
+
__name(lintAttr, "lintAttr");
|
|
1193
|
+
function checkUrl(name, v, tag, ctx) {
|
|
1194
|
+
const s = staticValue(v);
|
|
1195
|
+
if (s === null) return;
|
|
1196
|
+
if (sanitizeUrl(s) === "#" && s.trim() !== "#" && s.trim() !== "") push(ctx, "error", "url", `unsafe ${name} on <${tag}>: '${s.slice(0, 40)}'`);
|
|
1197
|
+
if (/^https?:\/\//i.test(s) && (name === "src" || name === "poster")) push(ctx, "warning", "hardcoded-image", `<${tag} ${name}> hardcodes an external URL \u2014 images should come from a content field`);
|
|
1198
|
+
}
|
|
1199
|
+
__name(checkUrl, "checkUrl");
|
|
1200
|
+
function lintHtml(root, sectionType) {
|
|
1201
|
+
const ctx = { lint: [], sectionType, inButton: 0, inSvg: 0 };
|
|
1202
|
+
lintNodes(root, ctx);
|
|
1203
|
+
return ctx.lint;
|
|
1204
|
+
}
|
|
1205
|
+
__name(lintHtml, "lintHtml");
|
|
1206
|
+
|
|
1207
|
+
// ../variant-compiler/src/lint/css.ts
|
|
1208
|
+
var ALLOWED_URL_HOSTS = ["images.unsplash.com", "images.pexels.com"];
|
|
1209
|
+
function lintCss(css, sectionType, attrs = {}) {
|
|
1210
|
+
const out = [];
|
|
1211
|
+
const err = /* @__PURE__ */ __name((code, message) => out.push({ level: "error", code, message }), "err");
|
|
1212
|
+
const warn = /* @__PURE__ */ __name((code, message) => out.push({ level: "warning", code, message }), "warn");
|
|
1213
|
+
if ("is:global" in attrs) err("style-global", "<style is:global> is not allowed \u2014 variant CSS is always scoped");
|
|
1214
|
+
if ("define:vars" in attrs) err("style-define-vars", "<style define:vars> is not supported \u2014 use var(--color-*) / var(--radius) tokens");
|
|
1215
|
+
if (css.length > 64 * 1024) err("style-size", "style block exceeds 64 KB");
|
|
1216
|
+
const stripped = css.replace(/\/\*[\s\S]*?\*\//g, " ");
|
|
1217
|
+
if (/@import\b/i.test(stripped)) err("css-import", "@import is not allowed");
|
|
1218
|
+
if (/@font-face\b/i.test(stripped)) err("css-font-face", "@font-face is not allowed \u2014 fonts come from the site theme (var(--font-heading|--font-body))");
|
|
1219
|
+
if (/expression\s*\(/i.test(stripped)) err("css-expression", "expression() is not allowed");
|
|
1220
|
+
if (/behavior\s*:|-moz-binding\s*:/i.test(stripped)) err("css-behavior", "behavior/-moz-binding are not allowed");
|
|
1221
|
+
if (/\bposition\s*:\s*fixed\b/i.test(stripped) && sectionType !== "navbar" && sectionType !== "banner") err("fixed", "position: fixed is only allowed in navbar/banner variants");
|
|
1222
|
+
for (const m of stripped.matchAll(/url\s*\(\s*(['"]?)([^'")]+)\1\s*\)/gi)) {
|
|
1223
|
+
const u = m[2].trim();
|
|
1224
|
+
if (/^data:image\/svg\+xml/i.test(u)) continue;
|
|
1225
|
+
if (u.startsWith("#")) continue;
|
|
1226
|
+
if (/^https?:\/\//i.test(u)) {
|
|
1227
|
+
try {
|
|
1228
|
+
const host = new URL(u).host;
|
|
1229
|
+
if (ALLOWED_URL_HOSTS.includes(host)) {
|
|
1230
|
+
warn("css-url", `url(${host}) \u2014 prefer passing images through content fields`);
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
} catch {
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
err("css-url", `url(${u.slice(0, 40)}) is not allowed (data exfiltration risk) \u2014 only data:image/svg+xml or images.unsplash.com/images.pexels.com`);
|
|
1237
|
+
}
|
|
1238
|
+
const noVar = stripped.replace(/var\([^)]*\)/g, "");
|
|
1239
|
+
if (/#[0-9a-f]{3,8}\b/i.test(noVar) || /\b(rgb|hsl)a?\(/i.test(noVar)) warn("hardcoded-color", "CSS hardcodes colors \u2014 use var(--color-primary|--color-foreground|\u2026)");
|
|
1240
|
+
if (/border-radius\s*:\s*\d/i.test(noVar)) warn("hardcoded-radius", "CSS hardcodes border-radius \u2014 use var(--radius)");
|
|
1241
|
+
if (/font-family\s*:(?![^;]*var\(--font-)/i.test(stripped)) warn("hardcoded-font", "CSS hardcodes font-family \u2014 use var(--font-heading) / var(--font-body)");
|
|
1242
|
+
if (/:root\b|\bhtml\b|\bbody\b/i.test(noVar)) warn("css-root", ":root/html/body selectors are rewritten to the variant root");
|
|
1243
|
+
return out;
|
|
1244
|
+
}
|
|
1245
|
+
__name(lintCss, "lintCss");
|
|
1246
|
+
|
|
1247
|
+
// ../variant-compiler/src/lint/js.ts
|
|
1248
|
+
import * as acorn from "acorn";
|
|
1249
|
+
var JS_MAX_BYTES = 8 * 1024;
|
|
1250
|
+
var FORBIDDEN_IDENTS = /* @__PURE__ */ new Set([
|
|
1251
|
+
"fetch",
|
|
1252
|
+
"XMLHttpRequest",
|
|
1253
|
+
"WebSocket",
|
|
1254
|
+
"EventSource",
|
|
1255
|
+
"eval",
|
|
1256
|
+
"Function",
|
|
1257
|
+
"importScripts",
|
|
1258
|
+
"localStorage",
|
|
1259
|
+
"sessionStorage",
|
|
1260
|
+
"indexedDB",
|
|
1261
|
+
"postMessage",
|
|
1262
|
+
"open",
|
|
1263
|
+
"navigator",
|
|
1264
|
+
"cookieStore",
|
|
1265
|
+
"globalThis",
|
|
1266
|
+
"self",
|
|
1267
|
+
"top",
|
|
1268
|
+
"parent",
|
|
1269
|
+
"opener",
|
|
1270
|
+
"frames",
|
|
1271
|
+
"caches",
|
|
1272
|
+
"ServiceWorker",
|
|
1273
|
+
"Worker",
|
|
1274
|
+
"SharedWorker",
|
|
1275
|
+
"Reflect",
|
|
1276
|
+
"Proxy",
|
|
1277
|
+
"Symbol",
|
|
1278
|
+
"WeakRef",
|
|
1279
|
+
"FinalizationRegistry",
|
|
1280
|
+
"crypto",
|
|
1281
|
+
"Notification",
|
|
1282
|
+
"Atomics",
|
|
1283
|
+
"SharedArrayBuffer"
|
|
1284
|
+
]);
|
|
1285
|
+
var FORBIDDEN_MEMBERS = /* @__PURE__ */ new Set([
|
|
1286
|
+
"cookie",
|
|
1287
|
+
"write",
|
|
1288
|
+
"writeln",
|
|
1289
|
+
"top",
|
|
1290
|
+
"parent",
|
|
1291
|
+
"opener",
|
|
1292
|
+
"frames",
|
|
1293
|
+
"postMessage",
|
|
1294
|
+
"sendBeacon",
|
|
1295
|
+
"__proto__",
|
|
1296
|
+
"constructor",
|
|
1297
|
+
"prototype",
|
|
1298
|
+
"location",
|
|
1299
|
+
"history",
|
|
1300
|
+
"execCommand",
|
|
1301
|
+
"requestSubmit",
|
|
1302
|
+
"submit",
|
|
1303
|
+
"importNode",
|
|
1304
|
+
"adoptNode",
|
|
1305
|
+
"contentWindow",
|
|
1306
|
+
"contentDocument",
|
|
1307
|
+
"srcdoc",
|
|
1308
|
+
"outerHTML",
|
|
1309
|
+
"insertAdjacentHTML",
|
|
1310
|
+
"createContextualFragment",
|
|
1311
|
+
"referrer",
|
|
1312
|
+
"domain",
|
|
1313
|
+
"defineProperty",
|
|
1314
|
+
"getOwnPropertyDescriptor",
|
|
1315
|
+
"setPrototypeOf",
|
|
1316
|
+
"getPrototypeOf"
|
|
1317
|
+
]);
|
|
1318
|
+
var DYNAMIC_HTML = /* @__PURE__ */ new Set(["innerHTML", "outerHTML"]);
|
|
1319
|
+
var FORBIDDEN_CALLS = /* @__PURE__ */ new Set(["atob", "btoa", "unescape", "decodeURIComponent", "setTimeout", "setInterval"]);
|
|
1320
|
+
var CREATE_ELEMENT_BLOCK = /^(script|iframe|object|embed|link|meta|base|form|frame|style)$/i;
|
|
1321
|
+
var SET_ATTRIBUTE_BLOCK = /^(on[a-z]+|src|href|srcdoc|action|formaction|style|xlink:href)$/i;
|
|
1322
|
+
function lintScript(src, attrs = {}) {
|
|
1323
|
+
const out = [];
|
|
1324
|
+
const err = /* @__PURE__ */ __name((code, message, line) => out.push({ level: "error", code, message, line }), "err");
|
|
1325
|
+
if (!("is:inline" in attrs)) err("script-inline", "<script> must be `<script is:inline>`");
|
|
1326
|
+
if ("src" in attrs) err("script-src", "<script src> is not allowed");
|
|
1327
|
+
if ("define:vars" in attrs) err("script-define-vars", "<script define:vars> is not supported \u2014 read values from data-* attributes on your root");
|
|
1328
|
+
if ("type" in attrs && attrs.type !== "text/javascript" && attrs.type !== "module" && attrs.type !== true) err("script-type", `unsupported script type '${String(attrs.type)}'`);
|
|
1329
|
+
if (new TextEncoder().encode(src).length > JS_MAX_BYTES) err("script-size", `script exceeds ${JS_MAX_BYTES / 1024} KB`);
|
|
1330
|
+
if (/\\x[0-9a-f]{2}|\\u[0-9a-f]{4}|\\u\{/i.test(src)) err("script-obfuscation", "hex/unicode escape sequences are not allowed");
|
|
1331
|
+
if (/String\s*\.\s*fromCharCode|fromCodePoint/.test(src)) err("script-obfuscation", "String.fromCharCode is not allowed");
|
|
1332
|
+
if (/\bwith\s*\(/.test(src)) err("script-with", "`with` is not allowed");
|
|
1333
|
+
if (/\bimport\s*\(/.test(src) || /\bimport\s+[\w{*]/.test(src) || /\bexport\s+/.test(src)) err("script-import", "import/export are not allowed in inline scripts");
|
|
1334
|
+
if (/\bdebugger\b/.test(src)) err("script-debugger", "`debugger` is not allowed");
|
|
1335
|
+
let ast;
|
|
1336
|
+
try {
|
|
1337
|
+
ast = acorn.parse(src, { ecmaVersion: 2022, sourceType: "script", allowReturnOutsideFunction: true, locations: true });
|
|
1338
|
+
} catch (e) {
|
|
1339
|
+
err("script-syntax", `script syntax error: ${e.message}`);
|
|
1340
|
+
return out;
|
|
1341
|
+
}
|
|
1342
|
+
const lineOf2 = /* @__PURE__ */ __name((n) => n.loc?.start?.line, "lineOf");
|
|
1343
|
+
const isStrLit = /* @__PURE__ */ __name((n, re) => !!n && n.type === "Literal" && typeof n.value === "string" && re.test(n.value), "isStrLit");
|
|
1344
|
+
const propName = /* @__PURE__ */ __name((n) => {
|
|
1345
|
+
const prop = n.property;
|
|
1346
|
+
if (!n.computed && prop.type === "Identifier") return prop.name;
|
|
1347
|
+
if (prop.type === "Literal" && typeof prop.value === "string") return prop.value;
|
|
1348
|
+
return null;
|
|
1349
|
+
}, "propName");
|
|
1350
|
+
const visit = /* @__PURE__ */ __name((n, parent) => {
|
|
1351
|
+
if (!n || typeof n.type !== "string") return;
|
|
1352
|
+
switch (n.type) {
|
|
1353
|
+
case "Identifier": {
|
|
1354
|
+
const name = n.name;
|
|
1355
|
+
const isPropKey = parent && (parent.type === "MemberExpression" && parent.property === n && !parent.computed) || parent?.type === "Property" && parent.key === n && !parent.computed;
|
|
1356
|
+
if (!isPropKey && FORBIDDEN_IDENTS.has(name)) err("script-forbidden", `'${name}' is not allowed`, lineOf2(n));
|
|
1357
|
+
if (name === "window" && parent?.type === "MemberExpression" && parent.computed) err("script-forbidden", "computed access on window is not allowed", lineOf2(n));
|
|
1358
|
+
break;
|
|
1359
|
+
}
|
|
1360
|
+
case "MemberExpression": {
|
|
1361
|
+
const p = propName(n);
|
|
1362
|
+
if (n.computed && n.property.type !== "Literal") {
|
|
1363
|
+
const obj = n.object;
|
|
1364
|
+
if (obj.type === "Identifier" && /^(window|document|globalThis|self|Object|Array)$/.test(obj.name)) err("script-forbidden", `computed member access on ${obj.name} is not allowed`, lineOf2(n));
|
|
1365
|
+
}
|
|
1366
|
+
if (p && FORBIDDEN_MEMBERS.has(p)) err("script-forbidden", `'.${p}' is not allowed`, lineOf2(n));
|
|
1367
|
+
break;
|
|
1368
|
+
}
|
|
1369
|
+
case "AssignmentExpression": {
|
|
1370
|
+
const left = n.left;
|
|
1371
|
+
if (left.type === "MemberExpression") {
|
|
1372
|
+
const p = propName(left);
|
|
1373
|
+
if (p && DYNAMIC_HTML.has(p) && n.right.type !== "Literal") err("script-html", `assigning a non-literal to .${p} is not allowed`, lineOf2(n));
|
|
1374
|
+
if (p === "src" || p === "href" || p === "action") err("script-forbidden", `assigning .${p} is not allowed`, lineOf2(n));
|
|
1375
|
+
if (p && /^on[a-z]+$/.test(p)) err("script-forbidden", `assigning .${p} handlers is not allowed \u2014 use addEventListener`, lineOf2(n));
|
|
1376
|
+
}
|
|
1377
|
+
break;
|
|
1378
|
+
}
|
|
1379
|
+
case "CallExpression":
|
|
1380
|
+
case "NewExpression": {
|
|
1381
|
+
const callee = n.callee;
|
|
1382
|
+
const args = n.arguments;
|
|
1383
|
+
if (callee.type === "Identifier" && FORBIDDEN_CALLS.has(callee.name)) {
|
|
1384
|
+
if (callee.name === "setTimeout" || callee.name === "setInterval") {
|
|
1385
|
+
if (args[0]?.type === "Literal" || args[0]?.type === "TemplateLiteral") err("script-forbidden", `${callee.name} with a string argument is not allowed`, lineOf2(n));
|
|
1386
|
+
} else err("script-forbidden", `${callee.name}() is not allowed`, lineOf2(n));
|
|
1387
|
+
}
|
|
1388
|
+
if (callee.type === "MemberExpression") {
|
|
1389
|
+
const p = propName(callee);
|
|
1390
|
+
if (p === "createElement" && (args[0]?.type !== "Literal" || isStrLit(args[0], CREATE_ELEMENT_BLOCK))) err("script-forbidden", "createElement of script/iframe/link/form (or a non-literal tag) is not allowed", lineOf2(n));
|
|
1391
|
+
if (p === "createElementNS" && args[1] && (args[1].type !== "Literal" || isStrLit(args[1], CREATE_ELEMENT_BLOCK))) err("script-forbidden", "createElementNS of a non-literal or blocked tag is not allowed", lineOf2(n));
|
|
1392
|
+
if ((p === "setAttribute" || p === "setAttributeNS") && (args[0]?.type !== "Literal" || isStrLit(args[0], SET_ATTRIBUTE_BLOCK))) err("script-forbidden", "setAttribute of on*/src/href/style (or a non-literal name) is not allowed", lineOf2(n));
|
|
1393
|
+
if (p === "insertAdjacentHTML" && args[1]?.type !== "Literal") err("script-html", "insertAdjacentHTML with a non-literal is not allowed", lineOf2(n));
|
|
1394
|
+
if (p === "assign" || p === "replace") {
|
|
1395
|
+
const obj = callee.object;
|
|
1396
|
+
if (obj.type === "MemberExpression" && propName(obj) === "location") err("script-forbidden", "location.assign/replace is not allowed", lineOf2(n));
|
|
1397
|
+
}
|
|
1398
|
+
if (p === "requestFullscreen" || p === "showModal") err("script-forbidden", `${p}() is not allowed`, lineOf2(n));
|
|
1399
|
+
}
|
|
1400
|
+
if (n.type === "NewExpression" && callee.type === "Identifier" && /^(Function|Worker|SharedWorker|WebSocket|XMLHttpRequest|EventSource|Proxy|BroadcastChannel|MessageChannel)$/.test(callee.name)) err("script-forbidden", `new ${callee.name} is not allowed`, lineOf2(n));
|
|
1401
|
+
break;
|
|
1402
|
+
}
|
|
1403
|
+
case "WhileStatement":
|
|
1404
|
+
case "DoWhileStatement":
|
|
1405
|
+
case "ForStatement": {
|
|
1406
|
+
const test = n.test;
|
|
1407
|
+
const infinite = !test || test.type === "Literal" && !!test.value;
|
|
1408
|
+
if (infinite && !hasBreak(n.body)) err("script-loop", "unbounded loop without break", lineOf2(n));
|
|
1409
|
+
break;
|
|
1410
|
+
}
|
|
1411
|
+
case "MetaProperty":
|
|
1412
|
+
err("script-import", "import.meta is not allowed", lineOf2(n));
|
|
1413
|
+
break;
|
|
1414
|
+
case "ImportExpression":
|
|
1415
|
+
err("script-import", "dynamic import() is not allowed", lineOf2(n));
|
|
1416
|
+
break;
|
|
1417
|
+
case "TaggedTemplateExpression":
|
|
1418
|
+
err("script-forbidden", "tagged templates are not allowed", lineOf2(n));
|
|
1419
|
+
break;
|
|
1420
|
+
case "ThisExpression":
|
|
1421
|
+
break;
|
|
1422
|
+
default:
|
|
1423
|
+
break;
|
|
1424
|
+
}
|
|
1425
|
+
for (const key of Object.keys(n)) {
|
|
1426
|
+
if (key === "loc" || key === "type" || key === "start" || key === "end") continue;
|
|
1427
|
+
const v = n[key];
|
|
1428
|
+
if (Array.isArray(v)) v.forEach((c) => c && typeof c === "object" && visit(c, n));
|
|
1429
|
+
else if (v && typeof v === "object" && "type" in v) visit(v, n);
|
|
1430
|
+
}
|
|
1431
|
+
}, "visit");
|
|
1432
|
+
visit(ast);
|
|
1433
|
+
return out;
|
|
1434
|
+
}
|
|
1435
|
+
__name(lintScript, "lintScript");
|
|
1436
|
+
function hasBreak(n) {
|
|
1437
|
+
if (!n || typeof n !== "object") return false;
|
|
1438
|
+
if (n.type === "BreakStatement" || n.type === "ReturnStatement" || n.type === "ThrowStatement") return true;
|
|
1439
|
+
if (n.type === "FunctionExpression" || n.type === "ArrowFunctionExpression" || n.type === "FunctionDeclaration") return false;
|
|
1440
|
+
for (const key of Object.keys(n)) {
|
|
1441
|
+
if (key === "loc") continue;
|
|
1442
|
+
const v = n[key];
|
|
1443
|
+
if (Array.isArray(v)) {
|
|
1444
|
+
if (v.some((c) => hasBreak(c))) return true;
|
|
1445
|
+
} else if (v && typeof v === "object" && "type" in v && hasBreak(v)) return true;
|
|
1446
|
+
}
|
|
1447
|
+
return false;
|
|
1448
|
+
}
|
|
1449
|
+
__name(hasBreak, "hasBreak");
|
|
1450
|
+
|
|
1451
|
+
// ../variant-compiler/src/css/scope.ts
|
|
1452
|
+
var ROOT_PLACEHOLDER = "__WV_ROOT__";
|
|
1453
|
+
var RECURSE_AT = /^@(media|supports|container|layer|scope|document)\b/i;
|
|
1454
|
+
var VERBATIM_AT = /^@(keyframes|-webkit-keyframes|property|font-face|page|counter-style|font-feature-values|namespace|charset)\b/i;
|
|
1455
|
+
function scopeCss(css, root = ROOT_PLACEHOLDER) {
|
|
1456
|
+
return scopeBlock(stripComments2(css), root);
|
|
1457
|
+
}
|
|
1458
|
+
__name(scopeCss, "scopeCss");
|
|
1459
|
+
function stripComments2(s) {
|
|
1460
|
+
return s.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
1461
|
+
}
|
|
1462
|
+
__name(stripComments2, "stripComments");
|
|
1463
|
+
function scopeBlock(src, root) {
|
|
1464
|
+
let out = "";
|
|
1465
|
+
let i = 0;
|
|
1466
|
+
while (i < src.length) {
|
|
1467
|
+
let j = i;
|
|
1468
|
+
let inStr = null;
|
|
1469
|
+
for (; j < src.length; j++) {
|
|
1470
|
+
const ch = src[j];
|
|
1471
|
+
if (inStr) {
|
|
1472
|
+
if (ch === "\\") j++;
|
|
1473
|
+
else if (ch === inStr) inStr = null;
|
|
1474
|
+
continue;
|
|
1475
|
+
}
|
|
1476
|
+
if (ch === '"' || ch === "'") inStr = ch;
|
|
1477
|
+
else if (ch === "{" || ch === ";" || ch === "}") break;
|
|
1478
|
+
}
|
|
1479
|
+
const prelude = src.slice(i, j).trim();
|
|
1480
|
+
if (j >= src.length) {
|
|
1481
|
+
if (prelude) out += prelude;
|
|
1482
|
+
break;
|
|
1483
|
+
}
|
|
1484
|
+
if (src[j] === "}") {
|
|
1485
|
+
i = j + 1;
|
|
1486
|
+
continue;
|
|
1487
|
+
}
|
|
1488
|
+
if (src[j] === ";") {
|
|
1489
|
+
if (prelude) out += `${prelude};`;
|
|
1490
|
+
i = j + 1;
|
|
1491
|
+
continue;
|
|
1492
|
+
}
|
|
1493
|
+
const close = matchBrace2(src, j);
|
|
1494
|
+
const body = src.slice(j + 1, close < 0 ? src.length : close);
|
|
1495
|
+
i = close < 0 ? src.length : close + 1;
|
|
1496
|
+
if (!prelude) continue;
|
|
1497
|
+
if (prelude.startsWith("@")) {
|
|
1498
|
+
if (VERBATIM_AT.test(prelude)) out += `${prelude}{${body}}`;
|
|
1499
|
+
else if (RECURSE_AT.test(prelude)) out += `${prelude}{${scopeBlock(body, root)}}`;
|
|
1500
|
+
else out += `${prelude}{${scopeBlock(body, root)}}`;
|
|
1501
|
+
continue;
|
|
1502
|
+
}
|
|
1503
|
+
const selectors = splitSelectors(prelude).map((s) => scopeSelector(s.trim(), root)).filter(Boolean);
|
|
1504
|
+
if (!selectors.length) continue;
|
|
1505
|
+
if (body.includes("{")) {
|
|
1506
|
+
out += `${selectors.join(",")}{${scopeBlock(body, root)}}`;
|
|
1507
|
+
} else {
|
|
1508
|
+
out += `${selectors.join(",")}{${body.trim()}}`;
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
return out;
|
|
1512
|
+
}
|
|
1513
|
+
__name(scopeBlock, "scopeBlock");
|
|
1514
|
+
function matchBrace2(src, open) {
|
|
1515
|
+
let depth = 0;
|
|
1516
|
+
let inStr = null;
|
|
1517
|
+
for (let i = open; i < src.length; i++) {
|
|
1518
|
+
const ch = src[i];
|
|
1519
|
+
if (inStr) {
|
|
1520
|
+
if (ch === "\\") i++;
|
|
1521
|
+
else if (ch === inStr) inStr = null;
|
|
1522
|
+
continue;
|
|
1523
|
+
}
|
|
1524
|
+
if (ch === '"' || ch === "'") inStr = ch;
|
|
1525
|
+
else if (ch === "{") depth++;
|
|
1526
|
+
else if (ch === "}" && --depth === 0) return i;
|
|
1527
|
+
}
|
|
1528
|
+
return -1;
|
|
1529
|
+
}
|
|
1530
|
+
__name(matchBrace2, "matchBrace");
|
|
1531
|
+
function splitSelectors(s) {
|
|
1532
|
+
const out = [];
|
|
1533
|
+
let depth = 0;
|
|
1534
|
+
let cur = "";
|
|
1535
|
+
let inStr = null;
|
|
1536
|
+
for (const ch of s) {
|
|
1537
|
+
if (inStr) {
|
|
1538
|
+
cur += ch;
|
|
1539
|
+
if (ch === inStr) inStr = null;
|
|
1540
|
+
continue;
|
|
1541
|
+
}
|
|
1542
|
+
if (ch === '"' || ch === "'") inStr = ch;
|
|
1543
|
+
if (ch === "(" || ch === "[") depth++;
|
|
1544
|
+
if (ch === ")" || ch === "]") depth--;
|
|
1545
|
+
if (ch === "," && depth === 0) {
|
|
1546
|
+
out.push(cur);
|
|
1547
|
+
cur = "";
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
cur += ch;
|
|
1551
|
+
}
|
|
1552
|
+
if (cur.trim()) out.push(cur);
|
|
1553
|
+
return out;
|
|
1554
|
+
}
|
|
1555
|
+
__name(splitSelectors, "splitSelectors");
|
|
1556
|
+
function scopeSelector(sel, root) {
|
|
1557
|
+
if (!sel) return "";
|
|
1558
|
+
if (sel.startsWith("&")) return `${root} ${sel.slice(1).trim()}`.trim();
|
|
1559
|
+
if (sel.includes(ROOT_PLACEHOLDER)) return sel;
|
|
1560
|
+
const m = /^(:root|html|body)(?![\w-])/i.exec(sel);
|
|
1561
|
+
if (m) {
|
|
1562
|
+
const rest = sel.slice(m[0].length).replace(/^\s*(body)(?![\w-])/i, "");
|
|
1563
|
+
return `${root}${rest.startsWith(":") || rest.startsWith("[") || rest.startsWith(".") ? rest : rest ? ` ${rest.trim()}` : ""}`;
|
|
1564
|
+
}
|
|
1565
|
+
return `${root} ${sel}`;
|
|
1566
|
+
}
|
|
1567
|
+
__name(scopeSelector, "scopeSelector");
|
|
1568
|
+
|
|
1569
|
+
// ../variant-compiler/src/tailwind.ts
|
|
1570
|
+
import { compile } from "tailwindcss";
|
|
1571
|
+
|
|
1572
|
+
// ../variant-compiler/src/generated/tailwind-css.ts
|
|
1573
|
+
var TAILWIND_THEME_CSS = "@theme default {\n --font-sans:\n ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',\n 'Noto Color Emoji';\n --font-serif: ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif;\n --font-mono:\n ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',\n monospace;\n\n --color-red-50: oklch(97.1% 0.013 17.38);\n --color-red-100: oklch(93.6% 0.032 17.717);\n --color-red-200: oklch(88.5% 0.062 18.334);\n --color-red-300: oklch(80.8% 0.114 19.571);\n --color-red-400: oklch(70.4% 0.191 22.216);\n --color-red-500: oklch(63.7% 0.237 25.331);\n --color-red-600: oklch(57.7% 0.245 27.325);\n --color-red-700: oklch(50.5% 0.213 27.518);\n --color-red-800: oklch(44.4% 0.177 26.899);\n --color-red-900: oklch(39.6% 0.141 25.723);\n --color-red-950: oklch(25.8% 0.092 26.042);\n\n --color-orange-50: oklch(98% 0.016 73.684);\n --color-orange-100: oklch(95.4% 0.038 75.164);\n --color-orange-200: oklch(90.1% 0.076 70.697);\n --color-orange-300: oklch(83.7% 0.128 66.29);\n --color-orange-400: oklch(75% 0.183 55.934);\n --color-orange-500: oklch(70.5% 0.213 47.604);\n --color-orange-600: oklch(64.6% 0.222 41.116);\n --color-orange-700: oklch(55.3% 0.195 38.402);\n --color-orange-800: oklch(47% 0.157 37.304);\n --color-orange-900: oklch(40.8% 0.123 38.172);\n --color-orange-950: oklch(26.6% 0.079 36.259);\n\n --color-amber-50: oklch(98.7% 0.022 95.277);\n --color-amber-100: oklch(96.2% 0.059 95.617);\n --color-amber-200: oklch(92.4% 0.12 95.746);\n --color-amber-300: oklch(87.9% 0.169 91.605);\n --color-amber-400: oklch(82.8% 0.189 84.429);\n --color-amber-500: oklch(76.9% 0.188 70.08);\n --color-amber-600: oklch(66.6% 0.179 58.318);\n --color-amber-700: oklch(55.5% 0.163 48.998);\n --color-amber-800: oklch(47.3% 0.137 46.201);\n --color-amber-900: oklch(41.4% 0.112 45.904);\n --color-amber-950: oklch(27.9% 0.077 45.635);\n\n --color-yellow-50: oklch(98.7% 0.026 102.212);\n --color-yellow-100: oklch(97.3% 0.071 103.193);\n --color-yellow-200: oklch(94.5% 0.129 101.54);\n --color-yellow-300: oklch(90.5% 0.182 98.111);\n --color-yellow-400: oklch(85.2% 0.199 91.936);\n --color-yellow-500: oklch(79.5% 0.184 86.047);\n --color-yellow-600: oklch(68.1% 0.162 75.834);\n --color-yellow-700: oklch(55.4% 0.135 66.442);\n --color-yellow-800: oklch(47.6% 0.114 61.907);\n --color-yellow-900: oklch(42.1% 0.095 57.708);\n --color-yellow-950: oklch(28.6% 0.066 53.813);\n\n --color-lime-50: oklch(98.6% 0.031 120.757);\n --color-lime-100: oklch(96.7% 0.067 122.328);\n --color-lime-200: oklch(93.8% 0.127 124.321);\n --color-lime-300: oklch(89.7% 0.196 126.665);\n --color-lime-400: oklch(84.1% 0.238 128.85);\n --color-lime-500: oklch(76.8% 0.233 130.85);\n --color-lime-600: oklch(64.8% 0.2 131.684);\n --color-lime-700: oklch(53.2% 0.157 131.589);\n --color-lime-800: oklch(45.3% 0.124 130.933);\n --color-lime-900: oklch(40.5% 0.101 131.063);\n --color-lime-950: oklch(27.4% 0.072 132.109);\n\n --color-green-50: oklch(98.2% 0.018 155.826);\n --color-green-100: oklch(96.2% 0.044 156.743);\n --color-green-200: oklch(92.5% 0.084 155.995);\n --color-green-300: oklch(87.1% 0.15 154.449);\n --color-green-400: oklch(79.2% 0.209 151.711);\n --color-green-500: oklch(72.3% 0.219 149.579);\n --color-green-600: oklch(62.7% 0.194 149.214);\n --color-green-700: oklch(52.7% 0.154 150.069);\n --color-green-800: oklch(44.8% 0.119 151.328);\n --color-green-900: oklch(39.3% 0.095 152.535);\n --color-green-950: oklch(26.6% 0.065 152.934);\n\n --color-emerald-50: oklch(97.9% 0.021 166.113);\n --color-emerald-100: oklch(95% 0.052 163.051);\n --color-emerald-200: oklch(90.5% 0.093 164.15);\n --color-emerald-300: oklch(84.5% 0.143 164.978);\n --color-emerald-400: oklch(76.5% 0.177 163.223);\n --color-emerald-500: oklch(69.6% 0.17 162.48);\n --color-emerald-600: oklch(59.6% 0.145 163.225);\n --color-emerald-700: oklch(50.8% 0.118 165.612);\n --color-emerald-800: oklch(43.2% 0.095 166.913);\n --color-emerald-900: oklch(37.8% 0.077 168.94);\n --color-emerald-950: oklch(26.2% 0.051 172.552);\n\n --color-teal-50: oklch(98.4% 0.014 180.72);\n --color-teal-100: oklch(95.3% 0.051 180.801);\n --color-teal-200: oklch(91% 0.096 180.426);\n --color-teal-300: oklch(85.5% 0.138 181.071);\n --color-teal-400: oklch(77.7% 0.152 181.912);\n --color-teal-500: oklch(70.4% 0.14 182.503);\n --color-teal-600: oklch(60% 0.118 184.704);\n --color-teal-700: oklch(51.1% 0.096 186.391);\n --color-teal-800: oklch(43.7% 0.078 188.216);\n --color-teal-900: oklch(38.6% 0.063 188.416);\n --color-teal-950: oklch(27.7% 0.046 192.524);\n\n --color-cyan-50: oklch(98.4% 0.019 200.873);\n --color-cyan-100: oklch(95.6% 0.045 203.388);\n --color-cyan-200: oklch(91.7% 0.08 205.041);\n --color-cyan-300: oklch(86.5% 0.127 207.078);\n --color-cyan-400: oklch(78.9% 0.154 211.53);\n --color-cyan-500: oklch(71.5% 0.143 215.221);\n --color-cyan-600: oklch(60.9% 0.126 221.723);\n --color-cyan-700: oklch(52% 0.105 223.128);\n --color-cyan-800: oklch(45% 0.085 224.283);\n --color-cyan-900: oklch(39.8% 0.07 227.392);\n --color-cyan-950: oklch(30.2% 0.056 229.695);\n\n --color-sky-50: oklch(97.7% 0.013 236.62);\n --color-sky-100: oklch(95.1% 0.026 236.824);\n --color-sky-200: oklch(90.1% 0.058 230.902);\n --color-sky-300: oklch(82.8% 0.111 230.318);\n --color-sky-400: oklch(74.6% 0.16 232.661);\n --color-sky-500: oklch(68.5% 0.169 237.323);\n --color-sky-600: oklch(58.8% 0.158 241.966);\n --color-sky-700: oklch(50% 0.134 242.749);\n --color-sky-800: oklch(44.3% 0.11 240.79);\n --color-sky-900: oklch(39.1% 0.09 240.876);\n --color-sky-950: oklch(29.3% 0.066 243.157);\n\n --color-blue-50: oklch(97% 0.014 254.604);\n --color-blue-100: oklch(93.2% 0.032 255.585);\n --color-blue-200: oklch(88.2% 0.059 254.128);\n --color-blue-300: oklch(80.9% 0.105 251.813);\n --color-blue-400: oklch(70.7% 0.165 254.624);\n --color-blue-500: oklch(62.3% 0.214 259.815);\n --color-blue-600: oklch(54.6% 0.245 262.881);\n --color-blue-700: oklch(48.8% 0.243 264.376);\n --color-blue-800: oklch(42.4% 0.199 265.638);\n --color-blue-900: oklch(37.9% 0.146 265.522);\n --color-blue-950: oklch(28.2% 0.091 267.935);\n\n --color-indigo-50: oklch(96.2% 0.018 272.314);\n --color-indigo-100: oklch(93% 0.034 272.788);\n --color-indigo-200: oklch(87% 0.065 274.039);\n --color-indigo-300: oklch(78.5% 0.115 274.713);\n --color-indigo-400: oklch(67.3% 0.182 276.935);\n --color-indigo-500: oklch(58.5% 0.233 277.117);\n --color-indigo-600: oklch(51.1% 0.262 276.966);\n --color-indigo-700: oklch(45.7% 0.24 277.023);\n --color-indigo-800: oklch(39.8% 0.195 277.366);\n --color-indigo-900: oklch(35.9% 0.144 278.697);\n --color-indigo-950: oklch(25.7% 0.09 281.288);\n\n --color-violet-50: oklch(96.9% 0.016 293.756);\n --color-violet-100: oklch(94.3% 0.029 294.588);\n --color-violet-200: oklch(89.4% 0.057 293.283);\n --color-violet-300: oklch(81.1% 0.111 293.571);\n --color-violet-400: oklch(70.2% 0.183 293.541);\n --color-violet-500: oklch(60.6% 0.25 292.717);\n --color-violet-600: oklch(54.1% 0.281 293.009);\n --color-violet-700: oklch(49.1% 0.27 292.581);\n --color-violet-800: oklch(43.2% 0.232 292.759);\n --color-violet-900: oklch(38% 0.189 293.745);\n --color-violet-950: oklch(28.3% 0.141 291.089);\n\n --color-purple-50: oklch(97.7% 0.014 308.299);\n --color-purple-100: oklch(94.6% 0.033 307.174);\n --color-purple-200: oklch(90.2% 0.063 306.703);\n --color-purple-300: oklch(82.7% 0.119 306.383);\n --color-purple-400: oklch(71.4% 0.203 305.504);\n --color-purple-500: oklch(62.7% 0.265 303.9);\n --color-purple-600: oklch(55.8% 0.288 302.321);\n --color-purple-700: oklch(49.6% 0.265 301.924);\n --color-purple-800: oklch(43.8% 0.218 303.724);\n --color-purple-900: oklch(38.1% 0.176 304.987);\n --color-purple-950: oklch(29.1% 0.149 302.717);\n\n --color-fuchsia-50: oklch(97.7% 0.017 320.058);\n --color-fuchsia-100: oklch(95.2% 0.037 318.852);\n --color-fuchsia-200: oklch(90.3% 0.076 319.62);\n --color-fuchsia-300: oklch(83.3% 0.145 321.434);\n --color-fuchsia-400: oklch(74% 0.238 322.16);\n --color-fuchsia-500: oklch(66.7% 0.295 322.15);\n --color-fuchsia-600: oklch(59.1% 0.293 322.896);\n --color-fuchsia-700: oklch(51.8% 0.253 323.949);\n --color-fuchsia-800: oklch(45.2% 0.211 324.591);\n --color-fuchsia-900: oklch(40.1% 0.17 325.612);\n --color-fuchsia-950: oklch(29.3% 0.136 325.661);\n\n --color-pink-50: oklch(97.1% 0.014 343.198);\n --color-pink-100: oklch(94.8% 0.028 342.258);\n --color-pink-200: oklch(89.9% 0.061 343.231);\n --color-pink-300: oklch(82.3% 0.12 346.018);\n --color-pink-400: oklch(71.8% 0.202 349.761);\n --color-pink-500: oklch(65.6% 0.241 354.308);\n --color-pink-600: oklch(59.2% 0.249 0.584);\n --color-pink-700: oklch(52.5% 0.223 3.958);\n --color-pink-800: oklch(45.9% 0.187 3.815);\n --color-pink-900: oklch(40.8% 0.153 2.432);\n --color-pink-950: oklch(28.4% 0.109 3.907);\n\n --color-rose-50: oklch(96.9% 0.015 12.422);\n --color-rose-100: oklch(94.1% 0.03 12.58);\n --color-rose-200: oklch(89.2% 0.058 10.001);\n --color-rose-300: oklch(81% 0.117 11.638);\n --color-rose-400: oklch(71.2% 0.194 13.428);\n --color-rose-500: oklch(64.5% 0.246 16.439);\n --color-rose-600: oklch(58.6% 0.253 17.585);\n --color-rose-700: oklch(51.4% 0.222 16.935);\n --color-rose-800: oklch(45.5% 0.188 13.697);\n --color-rose-900: oklch(41% 0.159 10.272);\n --color-rose-950: oklch(27.1% 0.105 12.094);\n\n --color-slate-50: oklch(98.4% 0.003 247.858);\n --color-slate-100: oklch(96.8% 0.007 247.896);\n --color-slate-200: oklch(92.9% 0.013 255.508);\n --color-slate-300: oklch(86.9% 0.022 252.894);\n --color-slate-400: oklch(70.4% 0.04 256.788);\n --color-slate-500: oklch(55.4% 0.046 257.417);\n --color-slate-600: oklch(44.6% 0.043 257.281);\n --color-slate-700: oklch(37.2% 0.044 257.287);\n --color-slate-800: oklch(27.9% 0.041 260.031);\n --color-slate-900: oklch(20.8% 0.042 265.755);\n --color-slate-950: oklch(12.9% 0.042 264.695);\n\n --color-gray-50: oklch(98.5% 0.002 247.839);\n --color-gray-100: oklch(96.7% 0.003 264.542);\n --color-gray-200: oklch(92.8% 0.006 264.531);\n --color-gray-300: oklch(87.2% 0.01 258.338);\n --color-gray-400: oklch(70.7% 0.022 261.325);\n --color-gray-500: oklch(55.1% 0.027 264.364);\n --color-gray-600: oklch(44.6% 0.03 256.802);\n --color-gray-700: oklch(37.3% 0.034 259.733);\n --color-gray-800: oklch(27.8% 0.033 256.848);\n --color-gray-900: oklch(21% 0.034 264.665);\n --color-gray-950: oklch(13% 0.028 261.692);\n\n --color-zinc-50: oklch(98.5% 0 0);\n --color-zinc-100: oklch(96.7% 0.001 286.375);\n --color-zinc-200: oklch(92% 0.004 286.32);\n --color-zinc-300: oklch(87.1% 0.006 286.286);\n --color-zinc-400: oklch(70.5% 0.015 286.067);\n --color-zinc-500: oklch(55.2% 0.016 285.938);\n --color-zinc-600: oklch(44.2% 0.017 285.786);\n --color-zinc-700: oklch(37% 0.013 285.805);\n --color-zinc-800: oklch(27.4% 0.006 286.033);\n --color-zinc-900: oklch(21% 0.006 285.885);\n --color-zinc-950: oklch(14.1% 0.005 285.823);\n\n --color-neutral-50: oklch(98.5% 0 0);\n --color-neutral-100: oklch(97% 0 0);\n --color-neutral-200: oklch(92.2% 0 0);\n --color-neutral-300: oklch(87% 0 0);\n --color-neutral-400: oklch(70.8% 0 0);\n --color-neutral-500: oklch(55.6% 0 0);\n --color-neutral-600: oklch(43.9% 0 0);\n --color-neutral-700: oklch(37.1% 0 0);\n --color-neutral-800: oklch(26.9% 0 0);\n --color-neutral-900: oklch(20.5% 0 0);\n --color-neutral-950: oklch(14.5% 0 0);\n\n --color-stone-50: oklch(98.5% 0.001 106.423);\n --color-stone-100: oklch(97% 0.001 106.424);\n --color-stone-200: oklch(92.3% 0.003 48.717);\n --color-stone-300: oklch(86.9% 0.005 56.366);\n --color-stone-400: oklch(70.9% 0.01 56.259);\n --color-stone-500: oklch(55.3% 0.013 58.071);\n --color-stone-600: oklch(44.4% 0.011 73.639);\n --color-stone-700: oklch(37.4% 0.01 67.558);\n --color-stone-800: oklch(26.8% 0.007 34.298);\n --color-stone-900: oklch(21.6% 0.006 56.043);\n --color-stone-950: oklch(14.7% 0.004 49.25);\n\n --color-mauve-50: oklch(98.5% 0 0);\n --color-mauve-100: oklch(96% 0.003 325.6);\n --color-mauve-200: oklch(92.2% 0.005 325.62);\n --color-mauve-300: oklch(86.5% 0.012 325.68);\n --color-mauve-400: oklch(71.1% 0.019 323.02);\n --color-mauve-500: oklch(54.2% 0.034 322.5);\n --color-mauve-600: oklch(43.5% 0.029 321.78);\n --color-mauve-700: oklch(36.4% 0.029 323.89);\n --color-mauve-800: oklch(26.3% 0.024 320.12);\n --color-mauve-900: oklch(21.2% 0.019 322.12);\n --color-mauve-950: oklch(14.5% 0.008 326);\n\n --color-olive-50: oklch(98.8% 0.003 106.5);\n --color-olive-100: oklch(96.6% 0.005 106.5);\n --color-olive-200: oklch(93% 0.007 106.5);\n --color-olive-300: oklch(88% 0.011 106.6);\n --color-olive-400: oklch(73.7% 0.021 106.9);\n --color-olive-500: oklch(58% 0.031 107.3);\n --color-olive-600: oklch(46.6% 0.025 107.3);\n --color-olive-700: oklch(39.4% 0.023 107.4);\n --color-olive-800: oklch(28.6% 0.016 107.4);\n --color-olive-900: oklch(22.8% 0.013 107.4);\n --color-olive-950: oklch(15.3% 0.006 107.1);\n\n --color-mist-50: oklch(98.7% 0.002 197.1);\n --color-mist-100: oklch(96.3% 0.002 197.1);\n --color-mist-200: oklch(92.5% 0.005 214.3);\n --color-mist-300: oklch(87.2% 0.007 219.6);\n --color-mist-400: oklch(72.3% 0.014 214.4);\n --color-mist-500: oklch(56% 0.021 213.5);\n --color-mist-600: oklch(45% 0.017 213.2);\n --color-mist-700: oklch(37.8% 0.015 216);\n --color-mist-800: oklch(27.5% 0.011 216.9);\n --color-mist-900: oklch(21.8% 0.008 223.9);\n --color-mist-950: oklch(14.8% 0.004 228.8);\n\n --color-taupe-50: oklch(98.6% 0.002 67.8);\n --color-taupe-100: oklch(96% 0.002 17.2);\n --color-taupe-200: oklch(92.2% 0.005 34.3);\n --color-taupe-300: oklch(86.8% 0.007 39.5);\n --color-taupe-400: oklch(71.4% 0.014 41.2);\n --color-taupe-500: oklch(54.7% 0.021 43.1);\n --color-taupe-600: oklch(43.8% 0.017 39.3);\n --color-taupe-700: oklch(36.7% 0.016 35.7);\n --color-taupe-800: oklch(26.8% 0.011 36.5);\n --color-taupe-900: oklch(21.4% 0.009 43.1);\n --color-taupe-950: oklch(14.7% 0.004 49.3);\n\n --color-black: #000;\n --color-white: #fff;\n\n --spacing: 0.25rem;\n\n --breakpoint-sm: 40rem;\n --breakpoint-md: 48rem;\n --breakpoint-lg: 64rem;\n --breakpoint-xl: 80rem;\n --breakpoint-2xl: 96rem;\n\n --container-3xs: 16rem;\n --container-2xs: 18rem;\n --container-xs: 20rem;\n --container-sm: 24rem;\n --container-md: 28rem;\n --container-lg: 32rem;\n --container-xl: 36rem;\n --container-2xl: 42rem;\n --container-3xl: 48rem;\n --container-4xl: 56rem;\n --container-5xl: 64rem;\n --container-6xl: 72rem;\n --container-7xl: 80rem;\n\n --text-xs: 0.75rem;\n --text-xs--line-height: calc(1 / 0.75);\n --text-sm: 0.875rem;\n --text-sm--line-height: calc(1.25 / 0.875);\n --text-base: 1rem;\n --text-base--line-height: calc(1.5 / 1);\n --text-lg: 1.125rem;\n --text-lg--line-height: calc(1.75 / 1.125);\n --text-xl: 1.25rem;\n --text-xl--line-height: calc(1.75 / 1.25);\n --text-2xl: 1.5rem;\n --text-2xl--line-height: calc(2 / 1.5);\n --text-3xl: 1.875rem;\n --text-3xl--line-height: calc(2.25 / 1.875);\n --text-4xl: 2.25rem;\n --text-4xl--line-height: calc(2.5 / 2.25);\n --text-5xl: 3rem;\n --text-5xl--line-height: 1;\n --text-6xl: 3.75rem;\n --text-6xl--line-height: 1;\n --text-7xl: 4.5rem;\n --text-7xl--line-height: 1;\n --text-8xl: 6rem;\n --text-8xl--line-height: 1;\n --text-9xl: 8rem;\n --text-9xl--line-height: 1;\n\n --font-weight-thin: 100;\n --font-weight-extralight: 200;\n --font-weight-light: 300;\n --font-weight-normal: 400;\n --font-weight-medium: 500;\n --font-weight-semibold: 600;\n --font-weight-bold: 700;\n --font-weight-extrabold: 800;\n --font-weight-black: 900;\n\n --tracking-tighter: -0.05em;\n --tracking-tight: -0.025em;\n --tracking-normal: 0em;\n --tracking-wide: 0.025em;\n --tracking-wider: 0.05em;\n --tracking-widest: 0.1em;\n\n --leading-tight: 1.25;\n --leading-snug: 1.375;\n --leading-normal: 1.5;\n --leading-relaxed: 1.625;\n --leading-loose: 2;\n\n --radius-xs: 0.125rem;\n --radius-sm: 0.25rem;\n --radius-md: 0.375rem;\n --radius-lg: 0.5rem;\n --radius-xl: 0.75rem;\n --radius-2xl: 1rem;\n --radius-3xl: 1.5rem;\n --radius-4xl: 2rem;\n\n --shadow-2xs: 0 1px rgb(0 0 0 / 0.05);\n --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05);\n --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);\n --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);\n --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);\n --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);\n --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);\n\n --inset-shadow-2xs: inset 0 1px rgb(0 0 0 / 0.05);\n --inset-shadow-xs: inset 0 1px 1px rgb(0 0 0 / 0.05);\n --inset-shadow-sm: inset 0 2px 4px rgb(0 0 0 / 0.05);\n\n --drop-shadow-xs: 0 1px 1px rgb(0 0 0 / 0.05);\n --drop-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.15);\n --drop-shadow-md: 0 3px 3px rgb(0 0 0 / 0.12);\n --drop-shadow-lg: 0 4px 4px rgb(0 0 0 / 0.15);\n --drop-shadow-xl: 0 9px 7px rgb(0 0 0 / 0.1);\n --drop-shadow-2xl: 0 25px 25px rgb(0 0 0 / 0.15);\n\n --text-shadow-2xs: 0px 1px 0px rgb(0 0 0 / 0.15);\n --text-shadow-xs: 0px 1px 1px rgb(0 0 0 / 0.2);\n --text-shadow-sm:\n 0px 1px 0px rgb(0 0 0 / 0.075), 0px 1px 1px rgb(0 0 0 / 0.075), 0px 2px 2px rgb(0 0 0 / 0.075);\n --text-shadow-md:\n 0px 1px 1px rgb(0 0 0 / 0.1), 0px 1px 2px rgb(0 0 0 / 0.1), 0px 2px 4px rgb(0 0 0 / 0.1);\n --text-shadow-lg:\n 0px 1px 2px rgb(0 0 0 / 0.1), 0px 3px 2px rgb(0 0 0 / 0.1), 0px 4px 8px rgb(0 0 0 / 0.1);\n\n --ease-in: cubic-bezier(0.4, 0, 1, 1);\n --ease-out: cubic-bezier(0, 0, 0.2, 1);\n --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);\n\n --animate-spin: spin 1s linear infinite;\n --animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;\n --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;\n --animate-bounce: bounce 1s infinite;\n\n @keyframes spin {\n to {\n transform: rotate(360deg);\n }\n }\n\n @keyframes ping {\n 75%,\n 100% {\n transform: scale(2);\n opacity: 0;\n }\n }\n\n @keyframes pulse {\n 50% {\n opacity: 0.5;\n }\n }\n\n @keyframes bounce {\n 0%,\n 100% {\n transform: translateY(-25%);\n animation-timing-function: cubic-bezier(0.8, 0, 1, 1);\n }\n\n 50% {\n transform: none;\n animation-timing-function: cubic-bezier(0, 0, 0.2, 1);\n }\n }\n\n --blur-xs: 4px;\n --blur-sm: 8px;\n --blur-md: 12px;\n --blur-lg: 16px;\n --blur-xl: 24px;\n --blur-2xl: 40px;\n --blur-3xl: 64px;\n\n --perspective-dramatic: 100px;\n --perspective-near: 300px;\n --perspective-normal: 500px;\n --perspective-midrange: 800px;\n --perspective-distant: 1200px;\n\n --aspect-video: 16 / 9;\n\n --default-transition-duration: 150ms;\n --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n --default-font-family: --theme(--font-sans, initial);\n --default-font-feature-settings: --theme(--font-sans--font-feature-settings, initial);\n --default-font-variation-settings: --theme(--font-sans--font-variation-settings, initial);\n --default-mono-font-family: --theme(--font-mono, initial);\n --default-mono-font-feature-settings: --theme(--font-mono--font-feature-settings, initial);\n --default-mono-font-variation-settings: --theme(--font-mono--font-variation-settings, initial);\n}\n\n/* Deprecated */\n@theme default inline reference {\n --blur: 8px;\n --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);\n --shadow-inner: inset 0 2px 4px 0 rgb(0 0 0 / 0.05);\n --drop-shadow: 0 1px 2px rgb(0 0 0 / 0.1), 0 1px 1px rgb(0 0 0 / 0.06);\n --radius: 0.25rem;\n --max-width-prose: 65ch;\n}\n";
|
|
1574
|
+
var TAILWIND_UTILITIES_CSS = "@tailwind utilities;\n";
|
|
1575
|
+
|
|
1576
|
+
// ../variant-compiler/src/tailwind.ts
|
|
1577
|
+
var THEME_INPUT = `
|
|
1578
|
+
@import "tailwindcss/theme.css" theme(reference);
|
|
1579
|
+
@import "tailwindcss/utilities.css";
|
|
1580
|
+
@custom-variant dark (&:is([data-theme="dark"] *));
|
|
1581
|
+
@theme inline {
|
|
1582
|
+
--color-background: var(--background);
|
|
1583
|
+
--color-foreground: var(--foreground);
|
|
1584
|
+
--color-card: var(--card);
|
|
1585
|
+
--color-card-foreground: var(--card-foreground);
|
|
1586
|
+
--color-primary: var(--primary);
|
|
1587
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
1588
|
+
--color-secondary: var(--secondary);
|
|
1589
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
1590
|
+
--color-muted: var(--muted);
|
|
1591
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
1592
|
+
--color-accent: var(--accent);
|
|
1593
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
1594
|
+
--color-border: var(--border);
|
|
1595
|
+
--color-input: var(--input);
|
|
1596
|
+
--color-ring: var(--ring);
|
|
1597
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
1598
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
1599
|
+
--radius-lg: var(--radius);
|
|
1600
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
1601
|
+
}
|
|
1602
|
+
`;
|
|
1603
|
+
var FILES = {
|
|
1604
|
+
"tailwindcss/theme.css": TAILWIND_THEME_CSS,
|
|
1605
|
+
"tailwindcss/utilities.css": TAILWIND_UTILITIES_CSS
|
|
1606
|
+
};
|
|
1607
|
+
var compilerPromise = null;
|
|
1608
|
+
function getCompiler() {
|
|
1609
|
+
if (!compilerPromise) {
|
|
1610
|
+
compilerPromise = compile(THEME_INPUT, {
|
|
1611
|
+
base: "/",
|
|
1612
|
+
loadStylesheet: /* @__PURE__ */ __name(async (id) => ({ path: "/" + id, base: "/", content: FILES[id] ?? "" }), "loadStylesheet"),
|
|
1613
|
+
loadModule: /* @__PURE__ */ __name(async () => {
|
|
1614
|
+
throw new Error("WVF: JS modules are not available to Tailwind");
|
|
1615
|
+
}, "loadModule")
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
return compilerPromise;
|
|
1619
|
+
}
|
|
1620
|
+
__name(getCompiler, "getCompiler");
|
|
1621
|
+
var CANDIDATE_RE = /^[!@]?[A-Za-z0-9_][\w:/\[\].%#(),-]*$/;
|
|
1622
|
+
var MACRO_CLASSES = [
|
|
1623
|
+
"mx-auto w-full px-6",
|
|
1624
|
+
"inline-flex items-center justify-center font-medium transition-all duration-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
1625
|
+
"bg-primary text-primary-foreground hover:opacity-90 shadow-sm",
|
|
1626
|
+
"border border-border text-foreground hover:bg-muted",
|
|
1627
|
+
"text-foreground hover:bg-muted",
|
|
1628
|
+
"px-4 py-2 text-sm px-6 py-3 text-base px-8 py-4 text-lg",
|
|
1629
|
+
"gap-1.5 px-2.5 py-1 rounded-md border-dashed text-xs whitespace-nowrap font-mono break-all w-3.5 h-3.5 shrink-0",
|
|
1630
|
+
"mt-10 flex justify-start justify-center justify-end hover:border-primary/30 text-primary hover:underline w-4 h-4 relative"
|
|
1631
|
+
];
|
|
1632
|
+
function pickCandidates(tokens) {
|
|
1633
|
+
const set = /* @__PURE__ */ new Set();
|
|
1634
|
+
for (const raw of [...MACRO_CLASSES, ...tokens]) {
|
|
1635
|
+
for (const t of raw.split(/\s+/)) {
|
|
1636
|
+
if (!t || t.length > 96 || !CANDIDATE_RE.test(t)) continue;
|
|
1637
|
+
if (/url\(/i.test(t)) continue;
|
|
1638
|
+
set.add(t);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
return [...set].sort();
|
|
1642
|
+
}
|
|
1643
|
+
__name(pickCandidates, "pickCandidates");
|
|
1644
|
+
async function compileTailwind(candidates) {
|
|
1645
|
+
if (!candidates.length) return "";
|
|
1646
|
+
const c = await getCompiler();
|
|
1647
|
+
const css = c.build(candidates);
|
|
1648
|
+
return css.replace(/^\/\*![\s\S]*?\*\/\s*/, "");
|
|
1649
|
+
}
|
|
1650
|
+
__name(compileTailwind, "compileTailwind");
|
|
1651
|
+
|
|
1652
|
+
// ../variant-compiler/src/schema.ts
|
|
1653
|
+
function typeOf(s) {
|
|
1654
|
+
if (!s) return "unknown";
|
|
1655
|
+
const t = s.type;
|
|
1656
|
+
if (typeof t === "string") return t;
|
|
1657
|
+
if (Array.isArray(t)) return t.filter((x) => x !== "null")[0] ?? "unknown";
|
|
1658
|
+
if (s.anyOf || s.oneOf) {
|
|
1659
|
+
const alts = (s.anyOf ?? s.oneOf).map(typeOf).filter((x) => x !== "null");
|
|
1660
|
+
return alts[0] ?? "unknown";
|
|
1661
|
+
}
|
|
1662
|
+
return "unknown";
|
|
1663
|
+
}
|
|
1664
|
+
__name(typeOf, "typeOf");
|
|
1665
|
+
function deriveSchema(fields, props, base) {
|
|
1666
|
+
const lint = [];
|
|
1667
|
+
const readKeys = new Set(props.map((p) => p.key));
|
|
1668
|
+
const declared = new Map(fields.map((f) => [f.key, f]));
|
|
1669
|
+
const extProps = {};
|
|
1670
|
+
const usedFields = [];
|
|
1671
|
+
for (const f of fields) {
|
|
1672
|
+
if (!f.optional) lint.push({ level: "warning", code: "props-optional", message: `prop '${f.key}' should be optional (\`${f.key}?:\`) \u2014 content may omit it` });
|
|
1673
|
+
const baseField = base?.properties[f.key];
|
|
1674
|
+
if (baseField) {
|
|
1675
|
+
usedFields.push(f.key);
|
|
1676
|
+
const bt = typeOf(baseField);
|
|
1677
|
+
const ft = typeOf(f.schema);
|
|
1678
|
+
if (bt !== "unknown" && ft !== bt) lint.push({ level: "warning", code: "props-type-mismatch", message: `prop '${f.key}' is '${ft}' but the base schema has '${bt}' \u2014 the editor uses the base type` });
|
|
1679
|
+
continue;
|
|
1680
|
+
}
|
|
1681
|
+
extProps[f.key] = { ...f.schema, ...f.description ? { description: f.description } : {} };
|
|
1682
|
+
}
|
|
1683
|
+
for (const k of readKeys) {
|
|
1684
|
+
if (!declared.has(k)) lint.push({ level: "warning", code: "props-undeclared", message: `'${k}' is read from Astro.props but not declared in interface Props` });
|
|
1685
|
+
else if (!base?.properties[k] && !(k in extProps)) extProps[k] = { type: "string" };
|
|
1686
|
+
if (base?.properties[k] && !usedFields.includes(k)) usedFields.push(k);
|
|
1687
|
+
}
|
|
1688
|
+
const hiddenFields = base ? Object.keys(base.properties).filter((k) => !usedFields.includes(k)) : [];
|
|
1689
|
+
const extension = { type: "object", properties: extProps, additionalProperties: true };
|
|
1690
|
+
return { extension, hiddenFields, usedFields, lint };
|
|
1691
|
+
}
|
|
1692
|
+
__name(deriveSchema, "deriveSchema");
|
|
1693
|
+
|
|
1694
|
+
// ../variant-compiler/src/index.ts
|
|
1695
|
+
var COMPILER_VERSION = "0.1.0";
|
|
1696
|
+
var IR_MAX_BYTES = 256 * 1024;
|
|
1697
|
+
var CSS_MAX_BYTES = 64 * 1024;
|
|
1698
|
+
var STRICT_CODES = /* @__PURE__ */ new Set(["dead-text", "hardcoded-color", "hardcoded-radius", "hardcoded-font", "hardcoded-image", "img-alt", "props-optional"]);
|
|
1699
|
+
var BUILTIN_IDS = /* @__PURE__ */ new Set(["t", "img", "url", "editChrome", "Math", "String", "Number", "Boolean", "Array", "undefined"]);
|
|
1700
|
+
async function compile2(source, opts) {
|
|
1701
|
+
const lint = [];
|
|
1702
|
+
const fail = /* @__PURE__ */ __name((partial = {}) => ({
|
|
1703
|
+
ok: false,
|
|
1704
|
+
ir: null,
|
|
1705
|
+
schema: { type: "object", properties: {} },
|
|
1706
|
+
hiddenFields: [],
|
|
1707
|
+
usedFields: [],
|
|
1708
|
+
hasScript: false,
|
|
1709
|
+
lint,
|
|
1710
|
+
classCandidates: [],
|
|
1711
|
+
compilerVersion: COMPILER_VERSION,
|
|
1712
|
+
...partial
|
|
1713
|
+
}), "fail");
|
|
1714
|
+
const pe = /* @__PURE__ */ __name((e) => e instanceof ParseError ? { level: "error", code: e.code, message: e.message, line: e.line } : { level: "error", code: "internal", message: String(e?.message ?? e) }, "pe");
|
|
1715
|
+
const norm = source.replace(/\r\n?/g, "\n");
|
|
1716
|
+
const fm = /^\s*---[ \t]*\n([\s\S]*?)\n---[ \t]*\n?([\s\S]*)$/.exec(norm);
|
|
1717
|
+
if (!fm) {
|
|
1718
|
+
lint.push({ level: "error", code: "frontmatter", message: "file must start with a `---` frontmatter block containing `interface Props`", line: 1 });
|
|
1719
|
+
return fail();
|
|
1720
|
+
}
|
|
1721
|
+
const fmSrc = fm[1];
|
|
1722
|
+
const tplSrc = fm[2];
|
|
1723
|
+
const fmLineOffset = (norm.slice(0, fm.index + norm.slice(fm.index).indexOf("---")).match(/\n/g) ?? []).length + 1;
|
|
1724
|
+
const tplLineOffset = fmLineOffset + (fmSrc.match(/\n/g) ?? []).length + 2;
|
|
1725
|
+
let front;
|
|
1726
|
+
try {
|
|
1727
|
+
front = parseFrontmatter(fmSrc, fmLineOffset);
|
|
1728
|
+
} catch (e) {
|
|
1729
|
+
lint.push(pe(e));
|
|
1730
|
+
return fail();
|
|
1731
|
+
}
|
|
1732
|
+
let tpl;
|
|
1733
|
+
try {
|
|
1734
|
+
tpl = parseTemplate(tplSrc, tplLineOffset - 1);
|
|
1735
|
+
} catch (e) {
|
|
1736
|
+
lint.push(pe(e));
|
|
1737
|
+
return fail();
|
|
1738
|
+
}
|
|
1739
|
+
if (!tpl.root.some((n) => n.t !== "txt" || n.v.trim())) lint.push({ level: "error", code: "empty", message: "template has no markup" });
|
|
1740
|
+
const aliases = front.aliases;
|
|
1741
|
+
const declared = /* @__PURE__ */ new Set([...BUILTIN_IDS, ...front.props.map((p) => p.name), ...front.consts.map((c) => c.name), ...Object.keys(aliases)]);
|
|
1742
|
+
const renameId = /* @__PURE__ */ __name((n) => aliases[n] ?? n, "renameId");
|
|
1743
|
+
const checkIds = /* @__PURE__ */ __name((e, local) => walkIds(e, local, (name, scope) => {
|
|
1744
|
+
if (!declared.has(name) && !scope.has(name)) lint.push({ level: "error", code: "unknown-id", message: `'${name}' is not defined (props, const, or helpers t/img/url only)` });
|
|
1745
|
+
}), "checkIds");
|
|
1746
|
+
for (const p of front.props) if (p.def) {
|
|
1747
|
+
rewriteIds(p.def, renameId);
|
|
1748
|
+
checkIds(p.def, /* @__PURE__ */ new Set());
|
|
1749
|
+
}
|
|
1750
|
+
for (const c of front.consts) {
|
|
1751
|
+
rewriteIds(c.e, renameId);
|
|
1752
|
+
checkIds(c.e, /* @__PURE__ */ new Set());
|
|
1753
|
+
}
|
|
1754
|
+
walkNodes(tpl.root, (e) => {
|
|
1755
|
+
rewriteIds(e, renameId);
|
|
1756
|
+
checkIds(e, /* @__PURE__ */ new Set());
|
|
1757
|
+
});
|
|
1758
|
+
let userCss = "";
|
|
1759
|
+
for (const s of tpl.styles) {
|
|
1760
|
+
lint.push(...lintCss(s.text, opts.sectionType, s.attrs).map((m) => ({ ...m, line: m.line ?? s.line })));
|
|
1761
|
+
userCss += s.text + "\n";
|
|
1762
|
+
}
|
|
1763
|
+
if (tpl.scripts.length > 1) lint.push({ level: "error", code: "script-count", message: "only one <script is:inline> block is allowed" });
|
|
1764
|
+
const script = tpl.scripts[0]?.text.trim() ?? "";
|
|
1765
|
+
if (tpl.scripts[0]) lint.push(...lintScript(script, tpl.scripts[0].attrs).map((m) => ({ ...m, line: m.line != null ? m.line + tpl.scripts[0].line : tpl.scripts[0].line })));
|
|
1766
|
+
lint.push(...lintHtml(tpl.root, opts.sectionType));
|
|
1767
|
+
const strings = [];
|
|
1768
|
+
walkNodes(tpl.root, (e) => collectStrings(e, strings), (attrs) => {
|
|
1769
|
+
for (const v of Object.values(attrs)) if (typeof v === "string") strings.push(v);
|
|
1770
|
+
});
|
|
1771
|
+
for (const c of front.consts) collectStrings(c.e, strings);
|
|
1772
|
+
for (const p of front.props) if (p.def) collectStrings(p.def, strings);
|
|
1773
|
+
const classCandidates = pickCandidates(strings);
|
|
1774
|
+
let twCss = "";
|
|
1775
|
+
if (!opts.skipTailwind) {
|
|
1776
|
+
try {
|
|
1777
|
+
twCss = await compileTailwind(classCandidates);
|
|
1778
|
+
} catch (e) {
|
|
1779
|
+
lint.push({ level: "error", code: "tailwind", message: `Tailwind compile failed: ${e.message}` });
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
let css = "";
|
|
1783
|
+
try {
|
|
1784
|
+
css = scopeCss(twCss, ROOT_PLACEHOLDER) + (userCss.trim() ? "\n" + scopeCss(userCss, ROOT_PLACEHOLDER) : "");
|
|
1785
|
+
} catch (e) {
|
|
1786
|
+
lint.push({ level: "error", code: "css-parse", message: `could not scope CSS: ${e.message}` });
|
|
1787
|
+
}
|
|
1788
|
+
if (css.length > CSS_MAX_BYTES) lint.push({ level: "error", code: "css-size", message: `compiled CSS exceeds ${CSS_MAX_BYTES / 1024} KB` });
|
|
1789
|
+
const sch = deriveSchema(front.fields, front.props, opts.base ?? null);
|
|
1790
|
+
lint.push(...sch.lint);
|
|
1791
|
+
if (opts.strict) {
|
|
1792
|
+
for (const m of lint) if (m.level === "warning" && STRICT_CODES.has(m.code)) m.level = "error";
|
|
1793
|
+
}
|
|
1794
|
+
const ir = { v: 1, sectionType: opts.sectionType, props: front.props, consts: front.consts, root: tpl.root, css, script };
|
|
1795
|
+
const irBytes = JSON.stringify(ir).length;
|
|
1796
|
+
if (irBytes > IR_MAX_BYTES) lint.push({ level: "error", code: "ir-size", message: `compiled variant exceeds ${IR_MAX_BYTES / 1024} KB` });
|
|
1797
|
+
const ok = !lint.some((m) => m.level === "error");
|
|
1798
|
+
return {
|
|
1799
|
+
ok,
|
|
1800
|
+
ir: ok ? ir : null,
|
|
1801
|
+
schema: sch.extension,
|
|
1802
|
+
hiddenFields: sch.hiddenFields,
|
|
1803
|
+
usedFields: sch.usedFields,
|
|
1804
|
+
hasScript: script.length > 0,
|
|
1805
|
+
lint,
|
|
1806
|
+
classCandidates,
|
|
1807
|
+
compilerVersion: COMPILER_VERSION
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1810
|
+
__name(compile2, "compile");
|
|
1811
|
+
function walkNodes(nodes, onExpr, onAttrs) {
|
|
1812
|
+
for (const n of nodes) {
|
|
1813
|
+
if (n.t === "ex") onExpr(n.e);
|
|
1814
|
+
else if (n.t === "el" || n.t === "comp") {
|
|
1815
|
+
const attrs = n.t === "el" ? n.attrs : n.props;
|
|
1816
|
+
onAttrs?.(attrs);
|
|
1817
|
+
for (const v of Object.values(attrs)) {
|
|
1818
|
+
if (typeof v === "object" && v !== null) {
|
|
1819
|
+
const e = "list" in v ? v.list : v.e;
|
|
1820
|
+
onExpr(e);
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
walkNodes(n.ch, onExpr, onAttrs);
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
__name(walkNodes, "walkNodes");
|
|
1828
|
+
function rewriteIds(e, rename) {
|
|
1829
|
+
switch (e.k) {
|
|
1830
|
+
case "id":
|
|
1831
|
+
e.n = rename(e.n);
|
|
1832
|
+
break;
|
|
1833
|
+
case "mem":
|
|
1834
|
+
rewriteIds(e.o, rename);
|
|
1835
|
+
break;
|
|
1836
|
+
case "idx":
|
|
1837
|
+
rewriteIds(e.o, rename);
|
|
1838
|
+
rewriteIds(e.i, rename);
|
|
1839
|
+
break;
|
|
1840
|
+
case "call":
|
|
1841
|
+
rewriteIds(e.c, rename);
|
|
1842
|
+
e.a.forEach((a) => rewriteIds(a, rename));
|
|
1843
|
+
break;
|
|
1844
|
+
case "un":
|
|
1845
|
+
rewriteIds(e.a, rename);
|
|
1846
|
+
break;
|
|
1847
|
+
case "bin":
|
|
1848
|
+
rewriteIds(e.l, rename);
|
|
1849
|
+
rewriteIds(e.r, rename);
|
|
1850
|
+
break;
|
|
1851
|
+
case "cond":
|
|
1852
|
+
rewriteIds(e.t, rename);
|
|
1853
|
+
rewriteIds(e.a, rename);
|
|
1854
|
+
rewriteIds(e.b, rename);
|
|
1855
|
+
break;
|
|
1856
|
+
case "tpl":
|
|
1857
|
+
e.parts.forEach((p) => typeof p !== "string" && rewriteIds(p, rename));
|
|
1858
|
+
break;
|
|
1859
|
+
case "arr":
|
|
1860
|
+
e.items.forEach((a) => rewriteIds(a, rename));
|
|
1861
|
+
break;
|
|
1862
|
+
case "obj":
|
|
1863
|
+
e.props.forEach((p) => rewriteIds(p.v, rename));
|
|
1864
|
+
break;
|
|
1865
|
+
case "fn":
|
|
1866
|
+
rewriteIds(e.body, rename);
|
|
1867
|
+
break;
|
|
1868
|
+
case "jsx": {
|
|
1869
|
+
const visit = /* @__PURE__ */ __name((nodes) => {
|
|
1870
|
+
for (const n of nodes) {
|
|
1871
|
+
if (n.t === "ex") rewriteIds(n.e, rename);
|
|
1872
|
+
else if (n.t === "el" || n.t === "comp") {
|
|
1873
|
+
for (const v of Object.values(n.t === "el" ? n.attrs : n.props)) if (typeof v === "object" && v !== null) rewriteIds("list" in v ? v.list : v.e, rename);
|
|
1874
|
+
visit(n.ch);
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
}, "visit");
|
|
1878
|
+
visit(e.n);
|
|
1879
|
+
break;
|
|
1880
|
+
}
|
|
1881
|
+
default:
|
|
1882
|
+
break;
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
__name(rewriteIds, "rewriteIds");
|
|
1886
|
+
function walkIds(e, local, onId) {
|
|
1887
|
+
switch (e.k) {
|
|
1888
|
+
case "id":
|
|
1889
|
+
onId(e.n, local);
|
|
1890
|
+
break;
|
|
1891
|
+
case "mem":
|
|
1892
|
+
walkIds(e.o, local, onId);
|
|
1893
|
+
break;
|
|
1894
|
+
case "idx":
|
|
1895
|
+
walkIds(e.o, local, onId);
|
|
1896
|
+
walkIds(e.i, local, onId);
|
|
1897
|
+
break;
|
|
1898
|
+
case "call":
|
|
1899
|
+
walkIds(e.c, local, onId);
|
|
1900
|
+
e.a.forEach((a) => walkIds(a, local, onId));
|
|
1901
|
+
break;
|
|
1902
|
+
case "un":
|
|
1903
|
+
walkIds(e.a, local, onId);
|
|
1904
|
+
break;
|
|
1905
|
+
case "bin":
|
|
1906
|
+
walkIds(e.l, local, onId);
|
|
1907
|
+
walkIds(e.r, local, onId);
|
|
1908
|
+
break;
|
|
1909
|
+
case "cond":
|
|
1910
|
+
walkIds(e.t, local, onId);
|
|
1911
|
+
walkIds(e.a, local, onId);
|
|
1912
|
+
walkIds(e.b, local, onId);
|
|
1913
|
+
break;
|
|
1914
|
+
case "tpl":
|
|
1915
|
+
e.parts.forEach((p) => typeof p !== "string" && walkIds(p, local, onId));
|
|
1916
|
+
break;
|
|
1917
|
+
case "arr":
|
|
1918
|
+
e.items.forEach((a) => walkIds(a, local, onId));
|
|
1919
|
+
break;
|
|
1920
|
+
case "obj":
|
|
1921
|
+
e.props.forEach((p) => walkIds(p.v, local, onId));
|
|
1922
|
+
break;
|
|
1923
|
+
case "fn": {
|
|
1924
|
+
const inner = /* @__PURE__ */ new Set([...local, ...e.params]);
|
|
1925
|
+
walkIds(e.body, inner, onId);
|
|
1926
|
+
break;
|
|
1927
|
+
}
|
|
1928
|
+
case "jsx": {
|
|
1929
|
+
const visit = /* @__PURE__ */ __name((nodes) => {
|
|
1930
|
+
for (const n of nodes) {
|
|
1931
|
+
if (n.t === "ex") walkIds(n.e, local, onId);
|
|
1932
|
+
else if (n.t === "el" || n.t === "comp") {
|
|
1933
|
+
for (const v of Object.values(n.t === "el" ? n.attrs : n.props)) if (typeof v === "object" && v !== null) walkIds("list" in v ? v.list : v.e, local, onId);
|
|
1934
|
+
visit(n.ch);
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
}, "visit");
|
|
1938
|
+
visit(e.n);
|
|
1939
|
+
break;
|
|
1940
|
+
}
|
|
1941
|
+
default:
|
|
1942
|
+
break;
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
__name(walkIds, "walkIds");
|
|
1946
|
+
function collectStrings(e, out) {
|
|
1947
|
+
switch (e.k) {
|
|
1948
|
+
case "lit":
|
|
1949
|
+
if (typeof e.v === "string") out.push(e.v);
|
|
1950
|
+
break;
|
|
1951
|
+
case "tpl":
|
|
1952
|
+
e.parts.forEach((p) => typeof p === "string" ? out.push(p) : collectStrings(p, out));
|
|
1953
|
+
break;
|
|
1954
|
+
case "mem":
|
|
1955
|
+
collectStrings(e.o, out);
|
|
1956
|
+
break;
|
|
1957
|
+
case "idx":
|
|
1958
|
+
collectStrings(e.o, out);
|
|
1959
|
+
collectStrings(e.i, out);
|
|
1960
|
+
break;
|
|
1961
|
+
case "call":
|
|
1962
|
+
collectStrings(e.c, out);
|
|
1963
|
+
e.a.forEach((a) => collectStrings(a, out));
|
|
1964
|
+
break;
|
|
1965
|
+
case "un":
|
|
1966
|
+
collectStrings(e.a, out);
|
|
1967
|
+
break;
|
|
1968
|
+
case "bin":
|
|
1969
|
+
collectStrings(e.l, out);
|
|
1970
|
+
collectStrings(e.r, out);
|
|
1971
|
+
break;
|
|
1972
|
+
case "cond":
|
|
1973
|
+
collectStrings(e.t, out);
|
|
1974
|
+
collectStrings(e.a, out);
|
|
1975
|
+
collectStrings(e.b, out);
|
|
1976
|
+
break;
|
|
1977
|
+
case "arr":
|
|
1978
|
+
e.items.forEach((a) => collectStrings(a, out));
|
|
1979
|
+
break;
|
|
1980
|
+
case "obj":
|
|
1981
|
+
e.props.forEach((p) => {
|
|
1982
|
+
out.push(p.key);
|
|
1983
|
+
collectStrings(p.v, out);
|
|
1984
|
+
});
|
|
1985
|
+
break;
|
|
1986
|
+
case "fn":
|
|
1987
|
+
collectStrings(e.body, out);
|
|
1988
|
+
break;
|
|
1989
|
+
case "jsx": {
|
|
1990
|
+
const visit = /* @__PURE__ */ __name((nodes) => {
|
|
1991
|
+
for (const n of nodes) {
|
|
1992
|
+
if (n.t === "ex") collectStrings(n.e, out);
|
|
1993
|
+
else if (n.t === "el" || n.t === "comp") {
|
|
1994
|
+
for (const v of Object.values(n.t === "el" ? n.attrs : n.props)) {
|
|
1995
|
+
if (typeof v === "string") out.push(v);
|
|
1996
|
+
else if (typeof v === "object" && v !== null) collectStrings("list" in v ? v.list : v.e, out);
|
|
1997
|
+
}
|
|
1998
|
+
visit(n.ch);
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
}, "visit");
|
|
2002
|
+
visit(e.n);
|
|
2003
|
+
break;
|
|
2004
|
+
}
|
|
2005
|
+
default:
|
|
2006
|
+
break;
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
__name(collectStrings, "collectStrings");
|
|
2010
|
+
|
|
2011
|
+
// ../variant-compiler/src/runtime/eval.ts
|
|
2012
|
+
function isJsxValue(v) {
|
|
2013
|
+
return typeof v === "object" && v !== null && v.__wvJsx === true;
|
|
2014
|
+
}
|
|
2015
|
+
__name(isJsxValue, "isJsxValue");
|
|
2016
|
+
var FORBIDDEN_PROPS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
2017
|
+
var ARRAY_METHODS = /* @__PURE__ */ new Set(["map", "filter", "slice", "join", "includes", "indexOf", "some", "every", "find", "findIndex", "flat", "concat", "at"]);
|
|
2018
|
+
var STRING_METHODS = /* @__PURE__ */ new Set(["slice", "substring", "includes", "startsWith", "endsWith", "trim", "toUpperCase", "toLowerCase", "split", "replace", "replaceAll", "indexOf", "padStart", "padEnd", "charAt", "at", "repeat"]);
|
|
2019
|
+
var NUMBER_METHODS = /* @__PURE__ */ new Set(["toFixed", "toLocaleString", "toString"]);
|
|
2020
|
+
var MATH_FNS = /* @__PURE__ */ new Set(["min", "max", "floor", "ceil", "round", "abs", "trunc", "sign", "sqrt", "pow"]);
|
|
2021
|
+
var MATH_PROXY = Object.freeze({ __wvMath: true });
|
|
2022
|
+
var STEP_LIMIT = 2e5;
|
|
2023
|
+
var EvalBudget = class {
|
|
2024
|
+
static {
|
|
2025
|
+
__name(this, "EvalBudget");
|
|
2026
|
+
}
|
|
2027
|
+
steps = 0;
|
|
2028
|
+
tick() {
|
|
2029
|
+
if (++this.steps > STEP_LIMIT) throw new Error("WVF: expression step limit exceeded");
|
|
2030
|
+
}
|
|
2031
|
+
};
|
|
2032
|
+
function makeScope(parent) {
|
|
2033
|
+
return Object.create(parent ?? null);
|
|
2034
|
+
}
|
|
2035
|
+
__name(makeScope, "makeScope");
|
|
2036
|
+
function readProp(o, p) {
|
|
2037
|
+
if (o == null || FORBIDDEN_PROPS.has(p)) return void 0;
|
|
2038
|
+
if (Array.isArray(o) || typeof o === "string") return p === "length" ? o.length : void 0;
|
|
2039
|
+
if (typeof o === "object") return Object.prototype.hasOwnProperty.call(o, p) ? o[p] : void 0;
|
|
2040
|
+
return void 0;
|
|
2041
|
+
}
|
|
2042
|
+
__name(readProp, "readProp");
|
|
2043
|
+
function evalExpr(e, scope, budget) {
|
|
2044
|
+
budget.tick();
|
|
2045
|
+
switch (e.k) {
|
|
2046
|
+
case "lit":
|
|
2047
|
+
return e.v;
|
|
2048
|
+
case "id": {
|
|
2049
|
+
if (e.n in scope) return scope[e.n];
|
|
2050
|
+
if (e.n === "Math") return MATH_PROXY;
|
|
2051
|
+
if (e.n === "String") return String;
|
|
2052
|
+
if (e.n === "Number") return Number;
|
|
2053
|
+
if (e.n === "Boolean") return Boolean;
|
|
2054
|
+
if (e.n === "Array") return { __wvArray: true };
|
|
2055
|
+
return void 0;
|
|
2056
|
+
}
|
|
2057
|
+
case "mem":
|
|
2058
|
+
return readProp(evalExpr(e.o, scope, budget), e.p);
|
|
2059
|
+
case "idx": {
|
|
2060
|
+
const o = evalExpr(e.o, scope, budget);
|
|
2061
|
+
const i = evalExpr(e.i, scope, budget);
|
|
2062
|
+
if (o == null) return void 0;
|
|
2063
|
+
if (typeof i === "number" && (Array.isArray(o) || typeof o === "string")) return o[i];
|
|
2064
|
+
if (typeof i === "string") return readProp(o, i);
|
|
2065
|
+
return void 0;
|
|
2066
|
+
}
|
|
2067
|
+
case "call": {
|
|
2068
|
+
const a = e.a.map((x) => evalExpr(x, scope, budget));
|
|
2069
|
+
if (e.c.k === "mem") {
|
|
2070
|
+
const recv = evalExpr(e.c.o, scope, budget);
|
|
2071
|
+
const m = e.c.p;
|
|
2072
|
+
if (recv == null) return void 0;
|
|
2073
|
+
if (recv === MATH_PROXY) return MATH_FNS.has(m) ? Math[m](...a) : void 0;
|
|
2074
|
+
if (recv.__wvArray) return m === "isArray" ? Array.isArray(a[0]) : m === "from" ? Array.from(a[0] ?? []) : void 0;
|
|
2075
|
+
if (Array.isArray(recv) && ARRAY_METHODS.has(m)) {
|
|
2076
|
+
if (recv.length > 5e3) throw new Error("WVF: array too large");
|
|
2077
|
+
return recv[m](...a);
|
|
2078
|
+
}
|
|
2079
|
+
if (typeof recv === "string" && STRING_METHODS.has(m)) {
|
|
2080
|
+
if (m === "repeat" && typeof a[0] === "number" && a[0] > 1e3) return void 0;
|
|
2081
|
+
return recv[m](...a);
|
|
2082
|
+
}
|
|
2083
|
+
if (typeof recv === "number" && NUMBER_METHODS.has(m)) return recv[m](...a);
|
|
2084
|
+
return void 0;
|
|
2085
|
+
}
|
|
2086
|
+
const fn = evalExpr(e.c, scope, budget);
|
|
2087
|
+
return typeof fn === "function" ? fn(...a) : void 0;
|
|
2088
|
+
}
|
|
2089
|
+
case "un": {
|
|
2090
|
+
const v = evalExpr(e.a, scope, budget);
|
|
2091
|
+
if (e.op === "!") return !v;
|
|
2092
|
+
if (e.op === "-") return -v;
|
|
2093
|
+
if (e.op === "+") return +v;
|
|
2094
|
+
return typeof v;
|
|
2095
|
+
}
|
|
2096
|
+
case "bin": {
|
|
2097
|
+
const l = evalExpr(e.l, scope, budget);
|
|
2098
|
+
if (e.op === "&&") return l && evalExpr(e.r, scope, budget);
|
|
2099
|
+
if (e.op === "||") return l || evalExpr(e.r, scope, budget);
|
|
2100
|
+
if (e.op === "??") return l ?? evalExpr(e.r, scope, budget);
|
|
2101
|
+
const r = evalExpr(e.r, scope, budget);
|
|
2102
|
+
switch (e.op) {
|
|
2103
|
+
case "===":
|
|
2104
|
+
return l === r;
|
|
2105
|
+
case "!==":
|
|
2106
|
+
return l !== r;
|
|
2107
|
+
case "==":
|
|
2108
|
+
return l == r;
|
|
2109
|
+
case "!=":
|
|
2110
|
+
return l != r;
|
|
2111
|
+
case "<":
|
|
2112
|
+
return l < r;
|
|
2113
|
+
case ">":
|
|
2114
|
+
return l > r;
|
|
2115
|
+
case "<=":
|
|
2116
|
+
return l <= r;
|
|
2117
|
+
case ">=":
|
|
2118
|
+
return l >= r;
|
|
2119
|
+
case "+":
|
|
2120
|
+
return l + r;
|
|
2121
|
+
case "-":
|
|
2122
|
+
return l - r;
|
|
2123
|
+
case "*":
|
|
2124
|
+
return l * r;
|
|
2125
|
+
case "/":
|
|
2126
|
+
return l / r;
|
|
2127
|
+
case "%":
|
|
2128
|
+
return l % r;
|
|
2129
|
+
}
|
|
2130
|
+
return void 0;
|
|
2131
|
+
}
|
|
2132
|
+
case "cond":
|
|
2133
|
+
return evalExpr(e.t, scope, budget) ? evalExpr(e.a, scope, budget) : evalExpr(e.b, scope, budget);
|
|
2134
|
+
case "tpl":
|
|
2135
|
+
return e.parts.map((x) => typeof x === "string" ? x : stringify(evalExpr(x, scope, budget))).join("");
|
|
2136
|
+
case "arr":
|
|
2137
|
+
return e.items.map((x) => evalExpr(x, scope, budget));
|
|
2138
|
+
case "obj": {
|
|
2139
|
+
const o = {};
|
|
2140
|
+
for (const pr of e.props) o[pr.key] = evalExpr(pr.v, scope, budget);
|
|
2141
|
+
return o;
|
|
2142
|
+
}
|
|
2143
|
+
case "fn":
|
|
2144
|
+
return (...args) => {
|
|
2145
|
+
const s = makeScope(scope);
|
|
2146
|
+
e.params.forEach((n, i) => {
|
|
2147
|
+
s[n] = args[i];
|
|
2148
|
+
});
|
|
2149
|
+
return evalExpr(e.body, s, budget);
|
|
2150
|
+
};
|
|
2151
|
+
case "jsx":
|
|
2152
|
+
return { __wvJsx: true, nodes: e.n, scope };
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
__name(evalExpr, "evalExpr");
|
|
2156
|
+
function stringify(v) {
|
|
2157
|
+
if (v == null || v === false || v === true) return "";
|
|
2158
|
+
if (typeof v === "string") return v;
|
|
2159
|
+
if (typeof v === "number") return Number.isFinite(v) ? String(v) : "";
|
|
2160
|
+
if (Array.isArray(v)) return v.map(stringify).join("");
|
|
2161
|
+
return "";
|
|
2162
|
+
}
|
|
2163
|
+
__name(stringify, "stringify");
|
|
2164
|
+
|
|
2165
|
+
// ../variant-compiler/src/runtime/serialize.ts
|
|
2166
|
+
var VOID = /* @__PURE__ */ new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "source", "track", "wbr"]);
|
|
2167
|
+
var URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "poster", "data-lightbox-src", "xlink:href"]);
|
|
2168
|
+
var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "form", "input", "textarea", "select", "link", "meta", "base", "style", "template", "frame", "frameset", "applet", "noscript", "html", "head", "body"]);
|
|
2169
|
+
function flattenClassList(v) {
|
|
2170
|
+
if (v == null || v === false || v === true) return "";
|
|
2171
|
+
if (typeof v === "string") return v;
|
|
2172
|
+
if (Array.isArray(v)) return v.map(flattenClassList).filter(Boolean).join(" ");
|
|
2173
|
+
if (typeof v === "object") {
|
|
2174
|
+
return Object.entries(v).filter(([, on]) => !!on).map(([k]) => k).join(" ");
|
|
2175
|
+
}
|
|
2176
|
+
return String(v);
|
|
2177
|
+
}
|
|
2178
|
+
__name(flattenClassList, "flattenClassList");
|
|
2179
|
+
function attrValue(val, scope, budget) {
|
|
2180
|
+
if (val === true) return true;
|
|
2181
|
+
if (typeof val === "string") return val;
|
|
2182
|
+
if ("list" in val) return flattenClassList(evalExpr(val.list, scope, budget));
|
|
2183
|
+
return evalExpr(val.e, scope, budget);
|
|
2184
|
+
}
|
|
2185
|
+
__name(attrValue, "attrValue");
|
|
2186
|
+
function attrsToString(attrs, scope, budget) {
|
|
2187
|
+
let out = "";
|
|
2188
|
+
const classes = [];
|
|
2189
|
+
for (const [rawName, val] of Object.entries(attrs)) {
|
|
2190
|
+
const name = rawName.toLowerCase();
|
|
2191
|
+
if (/^on/.test(name) || name === "srcdoc" || name.startsWith("set:") || name === "is:raw" || name === "define:vars") continue;
|
|
2192
|
+
if (name === "class:list") {
|
|
2193
|
+
classes.push(flattenClassList(attrValue(val, scope, budget)));
|
|
2194
|
+
continue;
|
|
2195
|
+
}
|
|
2196
|
+
const v = attrValue(val, scope, budget);
|
|
2197
|
+
if (name === "class") {
|
|
2198
|
+
classes.push(flattenClassList(v));
|
|
2199
|
+
continue;
|
|
2200
|
+
}
|
|
2201
|
+
if (v === false || v == null) continue;
|
|
2202
|
+
if (v === true) {
|
|
2203
|
+
out += ` ${name}`;
|
|
2204
|
+
continue;
|
|
2205
|
+
}
|
|
2206
|
+
let s = stringify(v);
|
|
2207
|
+
if (URL_ATTRS.has(name)) s = sanitizeUrl(s);
|
|
2208
|
+
if (name === "style" && /url\s*\(|expression\s*\(|@import|behavior\s*:/i.test(s)) continue;
|
|
2209
|
+
out += ` ${name}="${escAttr(s)}"`;
|
|
2210
|
+
}
|
|
2211
|
+
const cls = classes.filter(Boolean).join(" ");
|
|
2212
|
+
if (cls) out += ` class="${escAttr(cls)}"`;
|
|
2213
|
+
return out;
|
|
2214
|
+
}
|
|
2215
|
+
__name(attrsToString, "attrsToString");
|
|
2216
|
+
var BUTTON_BASE = "inline-flex items-center justify-center font-medium transition-all duration-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2";
|
|
2217
|
+
var BUTTON_VARIANTS = {
|
|
2218
|
+
default: "bg-primary text-primary-foreground hover:opacity-90 shadow-sm",
|
|
2219
|
+
outline: "border border-border text-foreground hover:bg-muted",
|
|
2220
|
+
ghost: "text-foreground hover:bg-muted"
|
|
2221
|
+
};
|
|
2222
|
+
var BUTTON_SIZES = { sm: "px-4 py-2 text-sm", default: "px-6 py-3 text-base", lg: "px-8 py-4 text-lg" };
|
|
2223
|
+
var VIEWMORE_STYLES = {
|
|
2224
|
+
outline: "px-6 py-3 text-sm font-medium border border-border text-foreground hover:bg-muted hover:border-primary/30",
|
|
2225
|
+
link: "text-sm font-medium text-primary hover:underline",
|
|
2226
|
+
solid: "px-6 py-3 text-sm font-medium bg-primary text-primary-foreground hover:opacity-90 shadow-sm"
|
|
2227
|
+
};
|
|
2228
|
+
function editUrlPillHtml(field, value, anchor = "below", extraClass = "") {
|
|
2229
|
+
const pos = anchor === "above" ? "bottom:100%;left:50%;transform:translateX(-50%);margin-bottom:4px;" : anchor === "right" ? "top:50%;left:100%;transform:translateY(-50%);margin-left:6px;" : anchor === "left" ? "top:50%;right:100%;transform:translateY(-50%);margin-right:6px;" : "top:100%;left:50%;transform:translateX(-50%);margin-top:4px;";
|
|
2230
|
+
return `<div data-edit-reveal data-anchor="${escAttr(anchor)}" class="edit-url-pill${extraClass ? " " + escAttr(extraClass) : ""}" style="display:none;position:absolute;z-index:10;pointer-events:auto;${pos}"><div class="edit-url-pill__inner inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md border border-dashed text-xs whitespace-nowrap shadow-sm" style="color:var(--color-background,#fff);border-color:color-mix(in srgb,var(--color-foreground,#000) 40%,transparent);background-color:var(--color-foreground,#000);text-transform:none;letter-spacing:normal;"><svg class="w-3.5 h-3.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244" /></svg><span data-edit-field="${escAttr(field)}" class="font-mono break-all">${escText(value)}</span></div></div>`;
|
|
2231
|
+
}
|
|
2232
|
+
__name(editUrlPillHtml, "editUrlPillHtml");
|
|
2233
|
+
function renderComponent(n, scope, ctx, budget) {
|
|
2234
|
+
const p = {};
|
|
2235
|
+
for (const [k, v] of Object.entries(n.props)) p[k] = attrValue(v, scope, budget);
|
|
2236
|
+
const inner = /* @__PURE__ */ __name(() => renderNodes(n.ch, scope, ctx, budget), "inner");
|
|
2237
|
+
switch (n.name) {
|
|
2238
|
+
case "Container":
|
|
2239
|
+
return `<div class="${escAttr(["mx-auto w-full px-6", flattenClassList(p.class)].filter(Boolean).join(" "))}" style="max-width: var(--site-max-width, 72rem);">${inner()}</div>`;
|
|
2240
|
+
case "Button": {
|
|
2241
|
+
const cls = [BUTTON_BASE, BUTTON_VARIANTS[String(p.variant ?? "default")] ?? BUTTON_VARIANTS.default, BUTTON_SIZES[String(p.size ?? "default")] ?? BUTTON_SIZES.default, flattenClassList(p.class)].filter(Boolean).join(" ");
|
|
2242
|
+
if (p.href != null) return `<a href="${escAttr(sanitizeUrl(stringify(p.href)))}" class="${escAttr(cls)}" data-track="cta">${inner()}</a>`;
|
|
2243
|
+
return `<button type="button" class="${escAttr(cls)}">${inner()}</button>`;
|
|
2244
|
+
}
|
|
2245
|
+
case "EditUrlPill":
|
|
2246
|
+
return editUrlPillHtml(stringify(p.field), stringify(p.value), stringify(p.anchor) || "below", flattenClassList(p.class));
|
|
2247
|
+
case "ViewMoreLink": {
|
|
2248
|
+
const url = stringify(p.url);
|
|
2249
|
+
if (!url) return "";
|
|
2250
|
+
const style = String(p.style ?? "outline");
|
|
2251
|
+
const align = String(p.align ?? "center");
|
|
2252
|
+
const justify = align === "left" ? "justify-start" : align === "right" ? "justify-end" : "justify-center";
|
|
2253
|
+
const cls = `relative inline-flex items-center gap-1.5 transition-all duration-200 ${VIEWMORE_STYLES[style] ?? VIEWMORE_STYLES.outline}`;
|
|
2254
|
+
const text = stringify(p.text) || "Lihat Selengkapnya";
|
|
2255
|
+
return `<div class="mt-10 flex ${justify}"><a href="${escAttr(sanitizeUrl(url))}" class="${escAttr(cls)}"${style === "link" ? "" : ' style="border-radius: var(--radius);"'} data-track="cta"><span data-edit-field="viewMoreText">${escText(text)}</span><svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" /></svg>${editUrlPillHtml("viewMoreUrl", url)}</a></div>`;
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
__name(renderComponent, "renderComponent");
|
|
2260
|
+
function renderValue(v, ctx, budget) {
|
|
2261
|
+
if (v == null || v === false || v === true) return "";
|
|
2262
|
+
if (isJsxValue(v)) return renderNodes(v.nodes, v.scope, ctx, budget);
|
|
2263
|
+
if (Array.isArray(v)) return v.map((x) => renderValue(x, ctx, budget)).join("");
|
|
2264
|
+
return escText(stringify(v));
|
|
2265
|
+
}
|
|
2266
|
+
__name(renderValue, "renderValue");
|
|
2267
|
+
function renderNodes(nodes, scope, ctx, budget) {
|
|
2268
|
+
let out = "";
|
|
2269
|
+
for (const n of nodes) out += renderNode(n, scope, ctx, budget);
|
|
2270
|
+
return out;
|
|
2271
|
+
}
|
|
2272
|
+
__name(renderNodes, "renderNodes");
|
|
2273
|
+
function renderNode(n, scope, ctx, budget) {
|
|
2274
|
+
budget.tick();
|
|
2275
|
+
switch (n.t) {
|
|
2276
|
+
case "txt":
|
|
2277
|
+
return escText(n.v);
|
|
2278
|
+
case "ex":
|
|
2279
|
+
return renderValue(evalExpr(n.e, scope, budget), ctx, budget);
|
|
2280
|
+
case "comp":
|
|
2281
|
+
return renderComponent(n, scope, ctx, budget);
|
|
2282
|
+
case "el": {
|
|
2283
|
+
const tag = n.tag.toLowerCase();
|
|
2284
|
+
if (BLOCKED_TAGS.has(tag) || !/^[a-z][a-z0-9-]*$/.test(tag)) return "";
|
|
2285
|
+
const editUrl = n.attrs["data-edit-url"];
|
|
2286
|
+
let attrs = n.attrs;
|
|
2287
|
+
let pill = "";
|
|
2288
|
+
if (editUrl !== void 0) {
|
|
2289
|
+
const { ["data-edit-url"]: _drop, ...rest } = n.attrs;
|
|
2290
|
+
attrs = rest;
|
|
2291
|
+
const field = stringify(attrValue(editUrl, scope, budget));
|
|
2292
|
+
const hrefVal = n.attrs.href !== void 0 ? stringify(attrValue(n.attrs.href, scope, budget)) : "";
|
|
2293
|
+
if (field) pill = editUrlPillHtml(field, hrefVal);
|
|
2294
|
+
}
|
|
2295
|
+
const open = `<${tag}${attrsToString(attrs, scope, budget)}>`;
|
|
2296
|
+
if (VOID.has(tag)) return open;
|
|
2297
|
+
return `${open}${renderNodes(n.ch, scope, ctx, budget)}${pill}</${tag}>`;
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
__name(renderNode, "renderNode");
|
|
2302
|
+
|
|
2303
|
+
// ../variant-compiler/src/runtime/index.ts
|
|
2304
|
+
var SHADOWED_GLOBALS = [
|
|
2305
|
+
"fetch",
|
|
2306
|
+
"XMLHttpRequest",
|
|
2307
|
+
"WebSocket",
|
|
2308
|
+
"EventSource",
|
|
2309
|
+
"eval",
|
|
2310
|
+
"Function",
|
|
2311
|
+
"localStorage",
|
|
2312
|
+
"sessionStorage",
|
|
2313
|
+
"indexedDB",
|
|
2314
|
+
"postMessage",
|
|
2315
|
+
"open",
|
|
2316
|
+
"importScripts",
|
|
2317
|
+
"navigator",
|
|
2318
|
+
"cookieStore",
|
|
2319
|
+
"caches"
|
|
2320
|
+
];
|
|
2321
|
+
function renderVariant(ir, content, ctx, opts = {}) {
|
|
2322
|
+
const uid = opts.uid ?? `wv-${Math.random().toString(36).slice(2, 8)}`;
|
|
2323
|
+
const budget = new EvalBudget();
|
|
2324
|
+
const scope = makeScope();
|
|
2325
|
+
scope.t = (key) => typeof key === "string" ? ctx.t(key) : "";
|
|
2326
|
+
scope.img = (url, w) => withUnsplashWidth(typeof url === "string" ? sanitizeUrl(url) : void 0, typeof w === "number" ? w : 960);
|
|
2327
|
+
scope.url = (u) => sanitizeUrl(typeof u === "string" ? u : "");
|
|
2328
|
+
scope.editChrome = ctx.editChrome;
|
|
2329
|
+
for (const p of ir.props) {
|
|
2330
|
+
const v = Object.prototype.hasOwnProperty.call(content, p.key) ? content[p.key] : void 0;
|
|
2331
|
+
scope[p.name] = v === void 0 && p.def ? evalExpr(p.def, scope, budget) : v;
|
|
2332
|
+
}
|
|
2333
|
+
for (const c of ir.consts) scope[c.name] = evalExpr(c.e, scope, budget);
|
|
2334
|
+
const html = renderNodes(ir.root, scope, ctx, budget);
|
|
2335
|
+
const css = ir.css ? ir.css.replace(/__WV_ROOT__/g, `[data-wv-inst="${uid}"]`) : "";
|
|
2336
|
+
const script = ir.script ? wrapScript(ir.script, uid) : "";
|
|
2337
|
+
return { html, css, script, uid };
|
|
2338
|
+
}
|
|
2339
|
+
__name(renderVariant, "renderVariant");
|
|
2340
|
+
function wrapScript(body, uid) {
|
|
2341
|
+
return `(function(${SHADOWED_GLOBALS.join(",")}){"use strict";var root=document.querySelector('[data-wv-inst="${uid}"]');if(!root)return;
|
|
2342
|
+
${body}
|
|
2343
|
+
})();`;
|
|
2344
|
+
}
|
|
2345
|
+
__name(wrapScript, "wrapScript");
|
|
2346
|
+
|
|
2347
|
+
// ../variant-compiler/src/preview.ts
|
|
2348
|
+
var PREVIEW_THEMES = {
|
|
2349
|
+
light: { background: "#ffffff", foreground: "#0f172a", card: "#f8fafc", muted: "#f1f5f9", mutedFg: "#64748b", border: "#e2e8f0", primary: "#3b82f6", primaryFg: "#ffffff", secondary: "#6366f1", accent: "#f59e0b", heading: "Inter, sans-serif", body: "Inter, sans-serif", radius: "8px" },
|
|
2350
|
+
dark: { background: "#0b1220", foreground: "#e5e7eb", card: "#111a2e", muted: "#16213a", mutedFg: "#9aa4b8", border: "#243049", primary: "#60a5fa", primaryFg: "#0b1220", secondary: "#a78bfa", accent: "#fbbf24", heading: "Inter, sans-serif", body: "Inter, sans-serif", radius: "12px" },
|
|
2351
|
+
warm: { background: "#fbf6ef", foreground: "#2b1d12", card: "#f4ebdf", muted: "#efe3d2", mutedFg: "#7a6652", border: "#e2d3bf", primary: "#b5562d", primaryFg: "#ffffff", secondary: "#7c4a2d", accent: "#d99a3f", heading: "Georgia, serif", body: "Inter, sans-serif", radius: "2px" }
|
|
2352
|
+
};
|
|
2353
|
+
function isPreviewThemeKey(v) {
|
|
2354
|
+
return typeof v === "string" && v in PREVIEW_THEMES;
|
|
2355
|
+
}
|
|
2356
|
+
__name(isPreviewThemeKey, "isPreviewThemeKey");
|
|
2357
|
+
function buildPreviewDoc(ir, content, themeKey, opts = {}) {
|
|
2358
|
+
const th = PREVIEW_THEMES[themeKey];
|
|
2359
|
+
const out = renderVariant(ir, content, { t: /* @__PURE__ */ __name((k) => k, "t"), editChrome: opts.editChrome ?? false }, { uid: "wv-preview" });
|
|
2360
|
+
const vars = `--background:${th.background};--foreground:${th.foreground};--card:${th.card};--card-foreground:${th.foreground};--primary:${th.primary};--primary-foreground:${th.primaryFg};--secondary:${th.secondary};--secondary-foreground:#fff;--muted:${th.muted};--muted-foreground:${th.mutedFg};--accent:${th.accent};--accent-foreground:#000;--border:${th.border};--input:${th.border};--ring:${th.primary};--radius:${th.radius};--font-heading:${th.heading};--font-body:${th.body};--gradient-from:${th.primary};--gradient-to:${th.secondary};--spacing:.25rem;--site-max-width:72rem;`;
|
|
2361
|
+
const tokens = `--color-background:var(--background);--color-foreground:var(--foreground);--color-card:var(--card);--color-card-foreground:var(--card-foreground);--color-primary:var(--primary);--color-primary-foreground:var(--primary-foreground);--color-secondary:var(--secondary);--color-secondary-foreground:var(--secondary-foreground);--color-muted:var(--muted);--color-muted-foreground:var(--muted-foreground);--color-accent:var(--accent);--color-accent-foreground:var(--accent-foreground);--color-border:var(--border);--color-input:var(--input);--color-ring:var(--ring);--radius-sm:calc(var(--radius) - 4px);--radius-md:calc(var(--radius) - 2px);--radius-lg:var(--radius);--radius-xl:calc(var(--radius) + 4px);`;
|
|
2362
|
+
return `<!doctype html><html data-theme="${themeKey === "dark" ? "dark" : "light"}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex">
|
|
2363
|
+
<style>*,::before,::after{box-sizing:border-box;border:0 solid var(--color-border)}html{font-size:16px;-webkit-text-size-adjust:100%}body{margin:0;font-family:var(--font-body);color:var(--foreground);background:var(--background);line-height:1.5}h1,h2,h3,h4,p{margin:0}h1{font-size:48px}h2{font-size:36px}h3{font-size:28px}img,video,svg{display:block;max-width:100%;height:auto}a{color:inherit;text-decoration:inherit}button{font:inherit;color:inherit;background:none}ul,ol{margin:0;padding:0;list-style:none}:root{${vars}${tokens}}.edit-url-pill{display:none!important}</style>
|
|
2364
|
+
<style>${out.css}</style></head><body><div data-wv-root data-wv-inst="${out.uid}">${out.html}</div>${out.script ? `<script>${out.script}</script>` : ""}</body></html>`;
|
|
2365
|
+
}
|
|
2366
|
+
__name(buildPreviewDoc, "buildPreviewDoc");
|
|
2367
|
+
export {
|
|
2368
|
+
COMPILER_VERSION,
|
|
2369
|
+
PREVIEW_THEMES,
|
|
2370
|
+
buildPreviewDoc,
|
|
2371
|
+
compile2 as compile,
|
|
2372
|
+
isPreviewThemeKey,
|
|
2373
|
+
renderVariant
|
|
2374
|
+
};
|