rainbowindex 0.4.0 → 0.4.1
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/CHANGELOG.md +92 -0
- package/README.md +5 -4
- package/dist/browser.mjs +1 -1
- package/dist/{chunk-4SVFFDS2.mjs → chunk-53WD6U2X.mjs} +226 -44
- package/dist/{chunk-6DAHUFNU.mjs → chunk-6GTG5SZJ.mjs} +872 -603
- package/dist/{chunk-TLR6RP5L.mjs → chunk-IYIUS7AE.mjs} +27 -10
- package/dist/{chunk-5Y7EXXLS.mjs → chunk-KRZL4IDK.mjs} +18 -5
- package/dist/{chunk-YLB6FGIG.mjs → chunk-WYMCN5OC.mjs} +2 -2
- package/dist/cli.mjs +5 -5
- package/dist/editor.d.ts +2 -2
- package/dist/editor.mjs +4 -4
- package/dist/{index-BB2HoeMj.d.ts → index-Dp6i5TSv.d.ts} +15 -11
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +4 -4
- package/dist/vite.mjs +36 -60
- package/package.json +2 -1
|
@@ -42,7 +42,91 @@ import {
|
|
|
42
42
|
registerCustomTextSizes,
|
|
43
43
|
registerCustomUtility,
|
|
44
44
|
snapshotCompilationContext
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-KRZL4IDK.mjs";
|
|
46
|
+
|
|
47
|
+
// src/shared.ts
|
|
48
|
+
function isRIDebug() {
|
|
49
|
+
return typeof process !== "undefined" && !!process.env.RI_DEBUG;
|
|
50
|
+
}
|
|
51
|
+
function withTimeout(promise, ms, message) {
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
const timer = setTimeout(() => reject(new Error(message)), ms);
|
|
54
|
+
if (typeof timer === "object" && "unref" in timer) timer.unref();
|
|
55
|
+
promise.then(
|
|
56
|
+
(value) => {
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
resolve(value);
|
|
59
|
+
},
|
|
60
|
+
(err) => {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
reject(err);
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function stripCSSComments(input) {
|
|
68
|
+
if (!input.includes("/")) return input;
|
|
69
|
+
const len = input.length;
|
|
70
|
+
let result = "";
|
|
71
|
+
let copyStart = 0;
|
|
72
|
+
let i = 0;
|
|
73
|
+
let quote = 0;
|
|
74
|
+
while (i < len) {
|
|
75
|
+
const c = input.charCodeAt(i);
|
|
76
|
+
if (quote !== 0) {
|
|
77
|
+
if (c === 92) {
|
|
78
|
+
i += 2;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (c === quote) quote = 0;
|
|
82
|
+
i++;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (c === 34 || c === 39) {
|
|
86
|
+
quote = c;
|
|
87
|
+
i++;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (c === 47) {
|
|
91
|
+
const next = input.charCodeAt(i + 1);
|
|
92
|
+
if (next === 42) {
|
|
93
|
+
result += input.slice(copyStart, i);
|
|
94
|
+
result += " ";
|
|
95
|
+
const end = input.indexOf("*/", i + 2);
|
|
96
|
+
copyStart = end === -1 ? len : end + 2;
|
|
97
|
+
if (end === -1) break;
|
|
98
|
+
i = copyStart;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (next === 47) {
|
|
102
|
+
result += input.slice(copyStart, i);
|
|
103
|
+
const end = input.indexOf("\n", i + 2);
|
|
104
|
+
copyStart = end === -1 ? len : end + 1;
|
|
105
|
+
if (end === -1) break;
|
|
106
|
+
result += "\n";
|
|
107
|
+
i = copyStart;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
i++;
|
|
112
|
+
}
|
|
113
|
+
if (copyStart === 0) return input;
|
|
114
|
+
if (copyStart < len) result += input.slice(copyStart);
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
var BOUNDARY_WHITESPACE_RE = /\s/;
|
|
118
|
+
function isAtRuleBoundary(css, idx) {
|
|
119
|
+
if (idx === 0) return true;
|
|
120
|
+
const prev = css.charCodeAt(idx - 1);
|
|
121
|
+
if (prev === 32 || prev >= 9 && prev <= 13 || prev === 59 || prev === 123 || prev === 125)
|
|
122
|
+
return true;
|
|
123
|
+
if (prev === 47 && idx >= 2 && css.charCodeAt(idx - 2) === 42) return true;
|
|
124
|
+
return prev > 127 && BOUNDARY_WHITESPACE_RE.test(css[idx - 1]);
|
|
125
|
+
}
|
|
126
|
+
var CSS_CUSTOM_IDENT_RE = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
|
|
127
|
+
function codepointCompare(a, b) {
|
|
128
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
129
|
+
}
|
|
46
130
|
|
|
47
131
|
// src/directives/foundation.ts
|
|
48
132
|
var DIRECTIVE_TYPE_NAMES = [
|
|
@@ -71,7 +155,29 @@ var DIRECTIVE_TYPE_NAMES = [
|
|
|
71
155
|
"register"
|
|
72
156
|
];
|
|
73
157
|
var REMOVAL_KEY = "--ri-rm";
|
|
158
|
+
var IDENT_KEY_RE = /^[\w-]+$/;
|
|
74
159
|
var WS_CHAR_RE = /\s/;
|
|
160
|
+
function topLevelIndexOf(str, char) {
|
|
161
|
+
let depth = 0;
|
|
162
|
+
for (let i = 0; i < str.length; i++) {
|
|
163
|
+
const c = str[i];
|
|
164
|
+
if (c === '"' || c === "'") {
|
|
165
|
+
i++;
|
|
166
|
+
while (i < str.length && str[i] !== c) {
|
|
167
|
+
if (str[i] === "\\") i++;
|
|
168
|
+
i++;
|
|
169
|
+
}
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (c === "(" || c === "[") depth++;
|
|
173
|
+
else if (c === ")" || c === "]") {
|
|
174
|
+
if (depth > 0) depth--;
|
|
175
|
+
} else if (c === char && depth === 0) {
|
|
176
|
+
return i;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return -1;
|
|
180
|
+
}
|
|
75
181
|
function* scanEntries(src, opts) {
|
|
76
182
|
let i = 0;
|
|
77
183
|
while (i < src.length) {
|
|
@@ -133,7 +239,7 @@ function* scanEntries(src, opts) {
|
|
|
133
239
|
const block = src.slice(i + 1, close).trim();
|
|
134
240
|
i = close + 1;
|
|
135
241
|
if (colonIdx === -1) {
|
|
136
|
-
if (value) yield { key: "", value, fragment: true };
|
|
242
|
+
if (value) yield { key: "", value, fragment: true, block };
|
|
137
243
|
continue;
|
|
138
244
|
}
|
|
139
245
|
if (key === REMOVAL_KEY) {
|
|
@@ -155,6 +261,70 @@ function* scanEntries(src, opts) {
|
|
|
155
261
|
yield { key, value };
|
|
156
262
|
}
|
|
157
263
|
}
|
|
264
|
+
function parseKeyValueBody(body, warnings, directiveName) {
|
|
265
|
+
const entries = [];
|
|
266
|
+
const removals = [];
|
|
267
|
+
const cleanedBody = stripCSSComments(body);
|
|
268
|
+
const flush = (raw) => {
|
|
269
|
+
const line = raw.trim();
|
|
270
|
+
if (!line) return;
|
|
271
|
+
if (line.startsWith("!")) {
|
|
272
|
+
removals.push(line.slice(1).trim());
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const colonIdx = line.indexOf(":");
|
|
276
|
+
if (colonIdx === -1) return;
|
|
277
|
+
const key = line.slice(0, colonIdx).trim();
|
|
278
|
+
const value = line.slice(colonIdx + 1).trim();
|
|
279
|
+
if (key === REMOVAL_KEY && value) {
|
|
280
|
+
removals.push(value);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (key && !IDENT_KEY_RE.test(key)) {
|
|
284
|
+
warnings?.push(
|
|
285
|
+
`[RI-1035] Invalid @${directiveName ?? "directive"} entry key "${key}" \u2014 keys may only contain letters, numbers, hyphens, and underscores. The entry was skipped.`
|
|
286
|
+
);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (key && value) {
|
|
290
|
+
entries.push([key, value]);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
let start = 0;
|
|
294
|
+
let depth = 0;
|
|
295
|
+
let lastNonWS = "";
|
|
296
|
+
for (let i = 0; i < cleanedBody.length; i++) {
|
|
297
|
+
const ch = cleanedBody[i];
|
|
298
|
+
if (ch === '"' || ch === "'") {
|
|
299
|
+
i++;
|
|
300
|
+
while (i < cleanedBody.length && cleanedBody[i] !== ch) {
|
|
301
|
+
if (cleanedBody[i] === "\\") i++;
|
|
302
|
+
i++;
|
|
303
|
+
}
|
|
304
|
+
lastNonWS = ch;
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (ch === "(" || ch === "[") {
|
|
308
|
+
depth++;
|
|
309
|
+
lastNonWS = ch;
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (ch === ")" || ch === "]") {
|
|
313
|
+
if (depth > 0) depth--;
|
|
314
|
+
lastNonWS = ch;
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (depth === 0 && (ch === ";" || ch === "\n" && lastNonWS !== ",")) {
|
|
318
|
+
flush(cleanedBody.slice(start, i));
|
|
319
|
+
start = i + 1;
|
|
320
|
+
lastNonWS = "";
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if (!WS_CHAR_RE.test(ch)) lastNonWS = ch;
|
|
324
|
+
}
|
|
325
|
+
flush(cleanedBody.slice(start));
|
|
326
|
+
return { entries, removals };
|
|
327
|
+
}
|
|
158
328
|
function findClosingBrace(src, start) {
|
|
159
329
|
let depth = 1;
|
|
160
330
|
for (let i = start + 1; i < src.length; i++) {
|
|
@@ -191,90 +361,6 @@ function hasApplyLikeDirective(src) {
|
|
|
191
361
|
}
|
|
192
362
|
var APPLY_LIKE_MATCH_RE = new RegExp(`@(?:${APPLY_LIKE_NAMES})\\s+([^;{}]+)`, "g");
|
|
193
363
|
|
|
194
|
-
// src/shared.ts
|
|
195
|
-
function isRIDebug() {
|
|
196
|
-
return typeof process !== "undefined" && !!process.env.RI_DEBUG;
|
|
197
|
-
}
|
|
198
|
-
function withTimeout(promise, ms, message) {
|
|
199
|
-
return new Promise((resolve, reject) => {
|
|
200
|
-
const timer = setTimeout(() => reject(new Error(message)), ms);
|
|
201
|
-
if (typeof timer === "object" && "unref" in timer) timer.unref();
|
|
202
|
-
promise.then(
|
|
203
|
-
(value) => {
|
|
204
|
-
clearTimeout(timer);
|
|
205
|
-
resolve(value);
|
|
206
|
-
},
|
|
207
|
-
(err) => {
|
|
208
|
-
clearTimeout(timer);
|
|
209
|
-
reject(err);
|
|
210
|
-
}
|
|
211
|
-
);
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
function stripCSSComments(input) {
|
|
215
|
-
if (!input.includes("/")) return input;
|
|
216
|
-
const len = input.length;
|
|
217
|
-
let result = "";
|
|
218
|
-
let copyStart = 0;
|
|
219
|
-
let i = 0;
|
|
220
|
-
let quote = 0;
|
|
221
|
-
while (i < len) {
|
|
222
|
-
const c = input.charCodeAt(i);
|
|
223
|
-
if (quote !== 0) {
|
|
224
|
-
if (c === 92) {
|
|
225
|
-
i += 2;
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
if (c === quote) quote = 0;
|
|
229
|
-
i++;
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
if (c === 34 || c === 39) {
|
|
233
|
-
quote = c;
|
|
234
|
-
i++;
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
if (c === 47) {
|
|
238
|
-
const next = input.charCodeAt(i + 1);
|
|
239
|
-
if (next === 42) {
|
|
240
|
-
result += input.slice(copyStart, i);
|
|
241
|
-
result += " ";
|
|
242
|
-
const end = input.indexOf("*/", i + 2);
|
|
243
|
-
copyStart = end === -1 ? len : end + 2;
|
|
244
|
-
if (end === -1) break;
|
|
245
|
-
i = copyStart;
|
|
246
|
-
continue;
|
|
247
|
-
}
|
|
248
|
-
if (next === 47) {
|
|
249
|
-
result += input.slice(copyStart, i);
|
|
250
|
-
const end = input.indexOf("\n", i + 2);
|
|
251
|
-
copyStart = end === -1 ? len : end + 1;
|
|
252
|
-
if (end === -1) break;
|
|
253
|
-
result += "\n";
|
|
254
|
-
i = copyStart;
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
i++;
|
|
259
|
-
}
|
|
260
|
-
if (copyStart === 0) return input;
|
|
261
|
-
if (copyStart < len) result += input.slice(copyStart);
|
|
262
|
-
return result;
|
|
263
|
-
}
|
|
264
|
-
var BOUNDARY_WHITESPACE_RE = /\s/;
|
|
265
|
-
function isAtRuleBoundary(css, idx) {
|
|
266
|
-
if (idx === 0) return true;
|
|
267
|
-
const prev = css.charCodeAt(idx - 1);
|
|
268
|
-
if (prev === 32 || prev >= 9 && prev <= 13 || prev === 59 || prev === 123 || prev === 125)
|
|
269
|
-
return true;
|
|
270
|
-
if (prev === 47 && idx >= 2 && css.charCodeAt(idx - 2) === 42) return true;
|
|
271
|
-
return prev > 127 && BOUNDARY_WHITESPACE_RE.test(css[idx - 1]);
|
|
272
|
-
}
|
|
273
|
-
var CSS_CUSTOM_IDENT_RE = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
|
|
274
|
-
function codepointCompare(a, b) {
|
|
275
|
-
return a < b ? -1 : a > b ? 1 : 0;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
364
|
// src/directives/activation.ts
|
|
279
365
|
var DIRECTIVE_NAMES_SET = /* @__PURE__ */ new Set([
|
|
280
366
|
...DIRECTIVE_TYPE_NAMES,
|
|
@@ -445,77 +531,8 @@ function mixColorAlpha(color, alpha) {
|
|
|
445
531
|
return `color-mix(in oklab, ${color} ${alpha}, transparent)`;
|
|
446
532
|
}
|
|
447
533
|
|
|
448
|
-
// src/
|
|
449
|
-
var SAFE_FONT_FAMILY_CHARS = "a-zA-Z0-9 ._-";
|
|
450
|
-
var SAFE_FONT_FAMILY_RE = new RegExp(`^[${SAFE_FONT_FAMILY_CHARS}]+$`);
|
|
451
|
-
var GOOGLE_DEFAULT_WEIGHT = "100 900";
|
|
452
|
-
var GOOGLE_DEFAULT_STYLE = "normal italic";
|
|
453
|
-
function kindFromProvider(provider) {
|
|
454
|
-
if (provider === "google") return "google";
|
|
455
|
-
if (provider === "system") return "system";
|
|
456
|
-
if (provider === "") return "manual";
|
|
457
|
-
return "local";
|
|
458
|
-
}
|
|
459
|
-
function createFontFace(partial) {
|
|
460
|
-
const isGoogle = partial.provider === "google";
|
|
461
|
-
return {
|
|
462
|
-
weight: isGoogle ? GOOGLE_DEFAULT_WEIGHT : "400",
|
|
463
|
-
style: isGoogle ? GOOGLE_DEFAULT_STYLE : "normal",
|
|
464
|
-
display: "swap",
|
|
465
|
-
subset: "latin",
|
|
466
|
-
...partial
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
function createFontSlot(partial) {
|
|
470
|
-
const defaultProvider = partial.kind === "google" ? "google" : partial.kind === "system" ? "system" : "";
|
|
471
|
-
const faces = partial.faces && partial.faces.length > 0 ? partial.faces.map((f) => ({ ...f })) : [createFontFace({ provider: defaultProvider })];
|
|
472
|
-
return {
|
|
473
|
-
slot: partial.slot,
|
|
474
|
-
family: partial.family,
|
|
475
|
-
kind: partial.kind ?? kindFromProvider(faces[0].provider),
|
|
476
|
-
fallback: [...partial.fallback ?? []],
|
|
477
|
-
features: partial.features ?? null,
|
|
478
|
-
variation: partial.variation ?? null,
|
|
479
|
-
preload: partial.preload ?? false,
|
|
480
|
-
faces,
|
|
481
|
-
metricsFallback: partial.metricsFallback,
|
|
482
|
-
sizeAdjust: partial.sizeAdjust,
|
|
483
|
-
ascent: partial.ascent,
|
|
484
|
-
descent: partial.descent,
|
|
485
|
-
lineGap: partial.lineGap
|
|
486
|
-
};
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// src/directives/parsers.ts
|
|
490
|
-
var UNSAFE_FONT_FAMILY_CHARS_RE = new RegExp(`[^${SAFE_FONT_FAMILY_CHARS}]`, "g");
|
|
491
|
-
var MAX_UTILITY_BODY_LENGTH = 1e4;
|
|
492
|
-
var MAX_CUSTOM_SELECTOR_LENGTH = 2e3;
|
|
534
|
+
// src/directives/color.ts
|
|
493
535
|
var MAX_COLOR_ENTRIES = 500;
|
|
494
|
-
var MAX_FONT_CONFIGS = 20;
|
|
495
|
-
var CUSTOM_VARIANT_NAME_RE = /^[a-z][\w-]*$/;
|
|
496
|
-
var IDENT_KEY_RE = /^[\w-]+$/;
|
|
497
|
-
var WS_CHAR_RE2 = /\s/;
|
|
498
|
-
function topLevelIndexOf(str, char) {
|
|
499
|
-
let depth = 0;
|
|
500
|
-
for (let i = 0; i < str.length; i++) {
|
|
501
|
-
const c = str[i];
|
|
502
|
-
if (c === '"' || c === "'") {
|
|
503
|
-
i++;
|
|
504
|
-
while (i < str.length && str[i] !== c) {
|
|
505
|
-
if (str[i] === "\\") i++;
|
|
506
|
-
i++;
|
|
507
|
-
}
|
|
508
|
-
continue;
|
|
509
|
-
}
|
|
510
|
-
if (c === "(" || c === "[") depth++;
|
|
511
|
-
else if (c === ")" || c === "]") {
|
|
512
|
-
if (depth > 0) depth--;
|
|
513
|
-
} else if (c === char && depth === 0) {
|
|
514
|
-
return i;
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
return -1;
|
|
518
|
-
}
|
|
519
536
|
function expandColorStopRef(v) {
|
|
520
537
|
if (v.includes("(") || v.startsWith("#")) return v;
|
|
521
538
|
const m = v.match(/^([\w][\w-]*)-(\d+)$/);
|
|
@@ -548,147 +565,6 @@ function expandColorSide(side) {
|
|
|
548
565
|
if (Number.isNaN(num)) return expanded;
|
|
549
566
|
return mixColorAlpha(expanded, clampAlphaPercent(num, isPercent));
|
|
550
567
|
}
|
|
551
|
-
var TRUTHY_FONT_VALUES = /* @__PURE__ */ new Set(["true", "yes", "on"]);
|
|
552
|
-
var FACE_DEFAULT_KEYS = /* @__PURE__ */ new Set([
|
|
553
|
-
"weight",
|
|
554
|
-
"style",
|
|
555
|
-
"display",
|
|
556
|
-
"subset",
|
|
557
|
-
"unicodeRange",
|
|
558
|
-
"unicode-range"
|
|
559
|
-
]);
|
|
560
|
-
function applyFaceOptions(face, entries) {
|
|
561
|
-
for (const [key, value] of entries) {
|
|
562
|
-
switch (key) {
|
|
563
|
-
case "weight":
|
|
564
|
-
face.weight = value;
|
|
565
|
-
face._weightExplicit = true;
|
|
566
|
-
break;
|
|
567
|
-
case "style":
|
|
568
|
-
face.style = value;
|
|
569
|
-
face._styleExplicit = true;
|
|
570
|
-
break;
|
|
571
|
-
case "display":
|
|
572
|
-
face.display = value;
|
|
573
|
-
break;
|
|
574
|
-
case "subset":
|
|
575
|
-
face.subset = value;
|
|
576
|
-
break;
|
|
577
|
-
case "unicodeRange":
|
|
578
|
-
case "unicode-range":
|
|
579
|
-
face.unicodeRange = value;
|
|
580
|
-
break;
|
|
581
|
-
case "preload":
|
|
582
|
-
face.preload = TRUTHY_FONT_VALUES.has(value);
|
|
583
|
-
break;
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
function applySlotOptions(slot, entries) {
|
|
588
|
-
for (const [key, value] of entries) {
|
|
589
|
-
switch (key) {
|
|
590
|
-
case "fallback":
|
|
591
|
-
slot.fallback = value.split(",").map((s) => s.trim());
|
|
592
|
-
break;
|
|
593
|
-
case "features":
|
|
594
|
-
slot.features = value;
|
|
595
|
-
break;
|
|
596
|
-
case "variation":
|
|
597
|
-
slot.variation = value;
|
|
598
|
-
break;
|
|
599
|
-
case "preload":
|
|
600
|
-
slot.preload = TRUTHY_FONT_VALUES.has(value);
|
|
601
|
-
break;
|
|
602
|
-
case "metricsFallback":
|
|
603
|
-
slot.metricsFallback = value.replace(/["']/g, "");
|
|
604
|
-
break;
|
|
605
|
-
case "sizeAdjust": {
|
|
606
|
-
const n = Number.parseFloat(value);
|
|
607
|
-
if (!Number.isNaN(n)) slot.sizeAdjust = n;
|
|
608
|
-
break;
|
|
609
|
-
}
|
|
610
|
-
case "ascent": {
|
|
611
|
-
const n = Number.parseFloat(value);
|
|
612
|
-
if (!Number.isNaN(n)) slot.ascent = n;
|
|
613
|
-
break;
|
|
614
|
-
}
|
|
615
|
-
case "descent": {
|
|
616
|
-
const n = Number.parseFloat(value);
|
|
617
|
-
if (!Number.isNaN(n)) slot.descent = n;
|
|
618
|
-
break;
|
|
619
|
-
}
|
|
620
|
-
case "lineGap": {
|
|
621
|
-
const n = Number.parseFloat(value);
|
|
622
|
-
if (!Number.isNaN(n)) slot.lineGap = n;
|
|
623
|
-
break;
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
function parseKeyValueBody(body, warnings, directiveName) {
|
|
629
|
-
const entries = [];
|
|
630
|
-
const removals = [];
|
|
631
|
-
const cleanedBody = stripCSSComments(body);
|
|
632
|
-
const flush = (raw) => {
|
|
633
|
-
const line = raw.trim();
|
|
634
|
-
if (!line) return;
|
|
635
|
-
if (line.startsWith("!")) {
|
|
636
|
-
removals.push(line.slice(1).trim());
|
|
637
|
-
return;
|
|
638
|
-
}
|
|
639
|
-
const colonIdx = line.indexOf(":");
|
|
640
|
-
if (colonIdx === -1) return;
|
|
641
|
-
const key = line.slice(0, colonIdx).trim();
|
|
642
|
-
const value = line.slice(colonIdx + 1).trim();
|
|
643
|
-
if (key === REMOVAL_KEY && value) {
|
|
644
|
-
removals.push(value);
|
|
645
|
-
return;
|
|
646
|
-
}
|
|
647
|
-
if (key && !IDENT_KEY_RE.test(key)) {
|
|
648
|
-
warnings?.push(
|
|
649
|
-
`[RI-1035] Invalid @${directiveName ?? "directive"} entry key "${key}" \u2014 keys may only contain letters, numbers, hyphens, and underscores. The entry was skipped.`
|
|
650
|
-
);
|
|
651
|
-
return;
|
|
652
|
-
}
|
|
653
|
-
if (key && value) {
|
|
654
|
-
entries.push([key, value]);
|
|
655
|
-
}
|
|
656
|
-
};
|
|
657
|
-
let start = 0;
|
|
658
|
-
let depth = 0;
|
|
659
|
-
let lastNonWS = "";
|
|
660
|
-
for (let i = 0; i < cleanedBody.length; i++) {
|
|
661
|
-
const ch = cleanedBody[i];
|
|
662
|
-
if (ch === '"' || ch === "'") {
|
|
663
|
-
i++;
|
|
664
|
-
while (i < cleanedBody.length && cleanedBody[i] !== ch) {
|
|
665
|
-
if (cleanedBody[i] === "\\") i++;
|
|
666
|
-
i++;
|
|
667
|
-
}
|
|
668
|
-
lastNonWS = ch;
|
|
669
|
-
continue;
|
|
670
|
-
}
|
|
671
|
-
if (ch === "(" || ch === "[") {
|
|
672
|
-
depth++;
|
|
673
|
-
lastNonWS = ch;
|
|
674
|
-
continue;
|
|
675
|
-
}
|
|
676
|
-
if (ch === ")" || ch === "]") {
|
|
677
|
-
if (depth > 0) depth--;
|
|
678
|
-
lastNonWS = ch;
|
|
679
|
-
continue;
|
|
680
|
-
}
|
|
681
|
-
if (depth === 0 && (ch === ";" || ch === "\n" && lastNonWS !== ",")) {
|
|
682
|
-
flush(cleanedBody.slice(start, i));
|
|
683
|
-
start = i + 1;
|
|
684
|
-
lastNonWS = "";
|
|
685
|
-
continue;
|
|
686
|
-
}
|
|
687
|
-
if (!WS_CHAR_RE2.test(ch)) lastNonWS = ch;
|
|
688
|
-
}
|
|
689
|
-
flush(cleanedBody.slice(start));
|
|
690
|
-
return { entries, removals };
|
|
691
|
-
}
|
|
692
568
|
function parseColorBody(body, warnings) {
|
|
693
569
|
const cleanedBody = stripCSSComments(body);
|
|
694
570
|
const colors = {};
|
|
@@ -759,104 +635,602 @@ function parseColorBody(body, warnings) {
|
|
|
759
635
|
`[RI-1108] @color "${key}" has an options block but its value is not generative \u2014 dark/inline/parabolic options only apply to "chroma hue" colors and were ignored.`
|
|
760
636
|
);
|
|
761
637
|
}
|
|
762
|
-
if (!Object.hasOwn(colors, key)) colorCount++;
|
|
763
|
-
colors[key] = def;
|
|
638
|
+
if (!Object.hasOwn(colors, key)) colorCount++;
|
|
639
|
+
colors[key] = def;
|
|
640
|
+
}
|
|
641
|
+
return { colors, removals };
|
|
642
|
+
}
|
|
643
|
+
function parseColorValue(key, value, warnings) {
|
|
644
|
+
const slashIdx = findPairSeparator(value);
|
|
645
|
+
if (slashIdx !== -1) {
|
|
646
|
+
const light = expandColorSide(value.slice(0, slashIdx).trim());
|
|
647
|
+
const dark = expandColorSide(value.slice(slashIdx + 1).trim());
|
|
648
|
+
return { type: "pair", light, dark };
|
|
649
|
+
}
|
|
650
|
+
if (/^light-dark\s*\(/.test(value)) {
|
|
651
|
+
const inner = value.slice(value.indexOf("(") + 1, value.lastIndexOf(")")).trim();
|
|
652
|
+
const commaIdx = topLevelIndexOf(inner, ",");
|
|
653
|
+
if (commaIdx !== -1) {
|
|
654
|
+
const light = expandColorSide(inner.slice(0, commaIdx).trim());
|
|
655
|
+
const dark = expandColorSide(inner.slice(commaIdx + 1).trim());
|
|
656
|
+
return { type: "pair", light, dark };
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
if (value === "transparent" || value === "currentColor" || value === "inherit") {
|
|
660
|
+
return { type: "keyword", value };
|
|
661
|
+
}
|
|
662
|
+
if (/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value)) {
|
|
663
|
+
return { type: "explicit", value };
|
|
664
|
+
}
|
|
665
|
+
if (/^(oklch|oklab|rgb|hsl|hwb|lch|lab|color|color-mix|var)\s*\(/.test(value)) {
|
|
666
|
+
return { type: "explicit", value };
|
|
667
|
+
}
|
|
668
|
+
const parts = value.split(/\s+/);
|
|
669
|
+
if (parts.length === 2) {
|
|
670
|
+
const chroma = Number.parseFloat(parts[0]);
|
|
671
|
+
const hue = Number.parseFloat(parts[1]);
|
|
672
|
+
if (!Number.isNaN(chroma) && !Number.isNaN(hue)) {
|
|
673
|
+
if (chroma < 0 || chroma > 0.4) {
|
|
674
|
+
warnings?.push(
|
|
675
|
+
`[RI-1102] @color "${key}" chroma ${chroma} is outside the typical range [0, 0.4] \u2014 clamping to valid range.`
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
const clampedChroma = Math.max(0, Math.min(0.4, chroma));
|
|
679
|
+
const normalizedHue = (hue % 360 + 360) % 360;
|
|
680
|
+
return { type: "generative", chroma: clampedChroma, hue: normalizedHue };
|
|
681
|
+
}
|
|
682
|
+
warnings?.push(
|
|
683
|
+
`[RI-1101] Invalid @color value "${key}: ${value}" \u2014 expected "chroma hue" (e.g., "0.15 30") or a CSS color function.`
|
|
684
|
+
);
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
if (parts.length === 1) {
|
|
688
|
+
const expanded = expandColorSide(value);
|
|
689
|
+
if (expanded !== value) return { type: "explicit", value: expanded };
|
|
690
|
+
if (/^[\w-]+$/.test(value)) return { type: "alias", source: value };
|
|
691
|
+
}
|
|
692
|
+
warnings?.push(
|
|
693
|
+
`[RI-1101] Invalid @color value "${key}: ${value}" \u2014 expected "chroma hue" (e.g., "0.15 30"), a color name alias, or a CSS color function.`
|
|
694
|
+
);
|
|
695
|
+
return null;
|
|
696
|
+
}
|
|
697
|
+
function parseDarkOverrideValue(value) {
|
|
698
|
+
const val = value.trim();
|
|
699
|
+
if (val === "mirror") return { strategy: "mirror" };
|
|
700
|
+
if (val === "fixed") return { strategy: "fixed" };
|
|
701
|
+
if (val.startsWith("shift")) {
|
|
702
|
+
const chromaMatch = val.match(/chroma\s+([+-]?\d+(?:\.\d+)?)/);
|
|
703
|
+
const hueMatch = val.match(/hue\s+([+-]?\d+(?:\.\d+)?)/);
|
|
704
|
+
const chromaDelta = chromaMatch ? Number.parseFloat(chromaMatch[1]) : 0;
|
|
705
|
+
const hueDelta = hueMatch ? Number.parseFloat(hueMatch[1]) : 0;
|
|
706
|
+
return {
|
|
707
|
+
strategy: "shift",
|
|
708
|
+
chromaDelta: Number.isNaN(chromaDelta) ? 0 : chromaDelta,
|
|
709
|
+
hueDelta: Number.isNaN(hueDelta) ? 0 : hueDelta
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
return void 0;
|
|
713
|
+
}
|
|
714
|
+
function findPairSeparator(value) {
|
|
715
|
+
let depth = 0;
|
|
716
|
+
for (let i = 0; i < value.length; i++) {
|
|
717
|
+
if (value[i] === "(" || value[i] === "[") depth++;
|
|
718
|
+
else if (value[i] === ")" || value[i] === "]") {
|
|
719
|
+
depth--;
|
|
720
|
+
if (depth < 0) return -1;
|
|
721
|
+
} else if (value[i] === "/" && depth === 0) {
|
|
722
|
+
const prev = value[i - 1];
|
|
723
|
+
const next = value[i + 1];
|
|
724
|
+
if (prev !== void 0 && !/\s/.test(prev) && next !== void 0 && /[\d.]/.test(next)) {
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
const before = value.slice(0, i).trim();
|
|
728
|
+
const after = value.slice(i + 1).trim();
|
|
729
|
+
if (before && after) {
|
|
730
|
+
return i;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return -1;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// src/integrations/font-providers/model.ts
|
|
738
|
+
var SAFE_FONT_FAMILY_CHARS = "a-zA-Z0-9 ._-";
|
|
739
|
+
var SAFE_FONT_FAMILY_RE = new RegExp(`^[${SAFE_FONT_FAMILY_CHARS}]+$`);
|
|
740
|
+
var GOOGLE_DEFAULT_WEIGHT = "100 900";
|
|
741
|
+
var GOOGLE_DEFAULT_STYLE = "normal italic";
|
|
742
|
+
function kindFromProvider(provider) {
|
|
743
|
+
if (provider === "google") return "google";
|
|
744
|
+
if (provider === "system") return "system";
|
|
745
|
+
if (provider === "") return "manual";
|
|
746
|
+
return "local";
|
|
747
|
+
}
|
|
748
|
+
function createFontFace(partial) {
|
|
749
|
+
const isGoogle = partial.provider === "google";
|
|
750
|
+
return {
|
|
751
|
+
weight: isGoogle ? GOOGLE_DEFAULT_WEIGHT : "400",
|
|
752
|
+
style: isGoogle ? GOOGLE_DEFAULT_STYLE : "normal",
|
|
753
|
+
display: "swap",
|
|
754
|
+
...partial
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
function createFontSlot(partial) {
|
|
758
|
+
const defaultProvider = partial.kind === "google" ? "google" : partial.kind === "system" ? "system" : "";
|
|
759
|
+
const faces = partial.faces && partial.faces.length > 0 ? partial.faces.map((f) => ({ ...f })) : [createFontFace({ provider: defaultProvider })];
|
|
760
|
+
const slot = {
|
|
761
|
+
slot: partial.slot,
|
|
762
|
+
family: partial.family,
|
|
763
|
+
kind: partial.kind ?? kindFromProvider(faces[0].provider),
|
|
764
|
+
fallback: [...partial.fallback ?? []],
|
|
765
|
+
features: partial.features ?? null,
|
|
766
|
+
variation: partial.variation ?? null,
|
|
767
|
+
faces
|
|
768
|
+
};
|
|
769
|
+
if (partial.metrics !== void 0) slot.metrics = partial.metrics;
|
|
770
|
+
return slot;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// src/directives/font.ts
|
|
774
|
+
var UNSAFE_FONT_FAMILY_CHARS_RE = new RegExp(`[^${SAFE_FONT_FAMILY_CHARS}]`, "g");
|
|
775
|
+
var MAX_FONT_CONFIGS = 20;
|
|
776
|
+
var TRUTHY_FONT_VALUES = /* @__PURE__ */ new Set(["true", "yes", "on"]);
|
|
777
|
+
function stripQuotes(s) {
|
|
778
|
+
return s.replace(/["']/g, "");
|
|
779
|
+
}
|
|
780
|
+
function sanitizeFamily(family) {
|
|
781
|
+
return family.replace(UNSAFE_FONT_FAMILY_CHARS_RE, "");
|
|
782
|
+
}
|
|
783
|
+
function sanitizeFallbacks(parts) {
|
|
784
|
+
return parts.map((s) => SAFE_FONT_FAMILY_RE.test(s) ? s : sanitizeFamily(s).trim()).filter((s) => s.length > 0);
|
|
785
|
+
}
|
|
786
|
+
function isUnsafeCSSValue(value) {
|
|
787
|
+
if (value.includes("\0")) return true;
|
|
788
|
+
for (let i = 0; i < value.length; i++) {
|
|
789
|
+
const c = value[i];
|
|
790
|
+
if (c === '"' || c === "'") {
|
|
791
|
+
i++;
|
|
792
|
+
while (i < value.length && value[i] !== c) {
|
|
793
|
+
if (value[i] === "\\") i++;
|
|
794
|
+
i++;
|
|
795
|
+
}
|
|
796
|
+
if (i >= value.length) return true;
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
if (c === "}") return true;
|
|
800
|
+
}
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
function dropUnsafeValue(key, value, slot, warnings) {
|
|
804
|
+
if (!isUnsafeCSSValue(value)) return false;
|
|
805
|
+
warnings?.push(
|
|
806
|
+
`[RI-1217] @font option "${key}" in slot "${slot}" has a value that can't be emitted safely into CSS \u2014 the entry was ignored.`
|
|
807
|
+
);
|
|
808
|
+
return true;
|
|
809
|
+
}
|
|
810
|
+
function warnDeprecated(warnings, slot, oldForm, replacement) {
|
|
811
|
+
warnings?.push(
|
|
812
|
+
`[RI-1218] @font slot "${slot}": ${oldForm} is deprecated \u2014 ${replacement}. The old form still works but will be removed.`
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
function topLevelFromIndex(s) {
|
|
816
|
+
let last = -1;
|
|
817
|
+
for (let i = 0; i < s.length; i++) {
|
|
818
|
+
const c = s[i];
|
|
819
|
+
if (c === '"' || c === "'") {
|
|
820
|
+
i++;
|
|
821
|
+
while (i < s.length && s[i] !== c) {
|
|
822
|
+
if (s[i] === "\\") i++;
|
|
823
|
+
i++;
|
|
824
|
+
}
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
if (c === "f" && i > 0 && s[i - 1] === " " && s.startsWith("from ", i)) last = i;
|
|
828
|
+
}
|
|
829
|
+
return last;
|
|
830
|
+
}
|
|
831
|
+
function splitTopLevelCommas(s) {
|
|
832
|
+
const parts = [];
|
|
833
|
+
let rest = s;
|
|
834
|
+
for (; ; ) {
|
|
835
|
+
const i = topLevelIndexOf(rest, ",");
|
|
836
|
+
if (i === -1) {
|
|
837
|
+
parts.push(rest);
|
|
838
|
+
return parts;
|
|
839
|
+
}
|
|
840
|
+
parts.push(rest.slice(0, i));
|
|
841
|
+
rest = rest.slice(i + 1);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
function parsePreamble(text, slot, warnings) {
|
|
845
|
+
const pre = text.replace(/\s+/g, " ").trim();
|
|
846
|
+
if (pre === "system") return { system: true, family: "", fallback: [] };
|
|
847
|
+
let stackText = pre;
|
|
848
|
+
let provider;
|
|
849
|
+
const fromIdx = topLevelFromIndex(pre);
|
|
850
|
+
if (fromIdx !== -1) {
|
|
851
|
+
const tail = stripQuotes(pre.slice(fromIdx + 4).trim());
|
|
852
|
+
if (tail) {
|
|
853
|
+
provider = tail;
|
|
854
|
+
stackText = pre.slice(0, fromIdx).trim();
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
const parts = splitTopLevelCommas(stackText).map((s) => stripQuotes(s.trim()).trim());
|
|
858
|
+
const family = parts[0] ?? "";
|
|
859
|
+
const fallback = sanitizeFallbacks(parts.slice(1));
|
|
860
|
+
if (provider === void 0 || provider === "google") {
|
|
861
|
+
return provider === "google" ? { family, fallback, google: true } : { family, fallback };
|
|
862
|
+
}
|
|
863
|
+
if (provider === "system") {
|
|
864
|
+
warnDeprecated(warnings, slot, "`from system`", "use the bare `system` keyword");
|
|
865
|
+
return { system: true, family: "", fallback: [] };
|
|
866
|
+
}
|
|
867
|
+
warnDeprecated(
|
|
868
|
+
warnings,
|
|
869
|
+
slot,
|
|
870
|
+
`\`from "${provider}"\``,
|
|
871
|
+
`declare the file as a face entry (\`face: ${provider};\`)`
|
|
872
|
+
);
|
|
873
|
+
return { family, fallback, legacyFaceSrc: provider };
|
|
874
|
+
}
|
|
875
|
+
function parseMetricsValue(value, slot, warnings) {
|
|
876
|
+
if (value === "none") return null;
|
|
877
|
+
const tokens = value.match(/"[^"]*"|'[^']*'|\S+/g) ?? [];
|
|
878
|
+
const familyParts = [];
|
|
879
|
+
const nums = [];
|
|
880
|
+
let malformed = false;
|
|
881
|
+
for (const t of tokens) {
|
|
882
|
+
const n = Number.parseFloat(t);
|
|
883
|
+
if (Number.isNaN(n)) {
|
|
884
|
+
if (nums.length > 0) {
|
|
885
|
+
malformed = true;
|
|
886
|
+
break;
|
|
887
|
+
}
|
|
888
|
+
familyParts.push(stripQuotes(t));
|
|
889
|
+
} else {
|
|
890
|
+
nums.push(n);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
const fallbackName = familyParts.join(" ").trim();
|
|
894
|
+
if (malformed || nums.length !== 0 && nums.length !== 4 || familyParts.length > 0 && fallbackName === "") {
|
|
895
|
+
warnings?.push(
|
|
896
|
+
`[RI-1220] @font slot "${slot}" has an invalid metrics value "${value}" \u2014 use \`metrics: none\`, \`metrics: "<local font>"\`, or \`metrics: "<local font>" <size-adjust> <ascent> <descent> <line-gap>\` (four percentages). The entry was ignored.`
|
|
897
|
+
);
|
|
898
|
+
return void 0;
|
|
899
|
+
}
|
|
900
|
+
const cfg = {};
|
|
901
|
+
if (fallbackName) cfg.fallback = fallbackName;
|
|
902
|
+
if (nums.length === 4) {
|
|
903
|
+
cfg.sizeAdjust = nums[0];
|
|
904
|
+
cfg.ascent = nums[1];
|
|
905
|
+
cfg.descent = nums[2];
|
|
906
|
+
cfg.lineGap = nums[3];
|
|
907
|
+
}
|
|
908
|
+
return cfg;
|
|
909
|
+
}
|
|
910
|
+
function normalizeFaceEntries(entries, slot, warnings) {
|
|
911
|
+
const out = [];
|
|
912
|
+
for (const [key, value] of entries) {
|
|
913
|
+
if (key === "unicodeRange") {
|
|
914
|
+
warnDeprecated(warnings, slot, "`unicodeRange:`", "use the CSS spelling `unicode-range:`");
|
|
915
|
+
out.push(["unicode-range", value]);
|
|
916
|
+
} else if (key === "subset") {
|
|
917
|
+
warnDeprecated(
|
|
918
|
+
warnings,
|
|
919
|
+
slot,
|
|
920
|
+
"`subset:`",
|
|
921
|
+
"remove the entry (the Google css2 API takes no subset hint)"
|
|
922
|
+
);
|
|
923
|
+
} else {
|
|
924
|
+
out.push([key, value]);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
return out;
|
|
928
|
+
}
|
|
929
|
+
function applyFaceOptions(face, entries, slot, warnings) {
|
|
930
|
+
for (const [key, value] of entries) {
|
|
931
|
+
if (dropUnsafeValue(key, value, slot, warnings)) continue;
|
|
932
|
+
switch (key) {
|
|
933
|
+
case "weight":
|
|
934
|
+
face.weight = value;
|
|
935
|
+
face._weightExplicit = true;
|
|
936
|
+
break;
|
|
937
|
+
case "style":
|
|
938
|
+
face.style = value;
|
|
939
|
+
face._styleExplicit = true;
|
|
940
|
+
break;
|
|
941
|
+
case "display":
|
|
942
|
+
face.display = value;
|
|
943
|
+
break;
|
|
944
|
+
case "unicode-range":
|
|
945
|
+
face.unicodeRange = value;
|
|
946
|
+
break;
|
|
947
|
+
case "preload":
|
|
948
|
+
face.preload = TRUTHY_FONT_VALUES.has(value);
|
|
949
|
+
break;
|
|
950
|
+
case "src":
|
|
951
|
+
warnings?.push(
|
|
952
|
+
`[RI-1217] Unknown @font option "src" in slot "${slot}" \u2014 the face source is the face: value itself (\`face: /path.woff2 { \u2026 }\`). The entry was ignored.`
|
|
953
|
+
);
|
|
954
|
+
break;
|
|
955
|
+
default:
|
|
956
|
+
warnings?.push(
|
|
957
|
+
`[RI-1217] Unknown @font option "${key}" in slot "${slot}" \u2014 the entry was ignored.`
|
|
958
|
+
);
|
|
959
|
+
break;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
var LEGACY_METRICS_KEYS = /* @__PURE__ */ new Set([
|
|
964
|
+
"metricsFallback",
|
|
965
|
+
"sizeAdjust",
|
|
966
|
+
"ascent",
|
|
967
|
+
"descent",
|
|
968
|
+
"lineGap"
|
|
969
|
+
]);
|
|
970
|
+
function parseSlotBody(body, slot, warnings) {
|
|
971
|
+
const out = { faceDefaults: [], faces: [], legacyMetrics: [] };
|
|
972
|
+
for (const entry of scanEntries(body, { newlineTerminates: true })) {
|
|
973
|
+
if (entry.unclosedBlock) {
|
|
974
|
+
warnings?.push(
|
|
975
|
+
`[RI-1217] @font slot "${slot}" has an unterminated { block after "${entry.key || entry.value}" \u2014 the entry was ignored.`
|
|
976
|
+
);
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
if (entry.fragment) {
|
|
980
|
+
if (entry.value === "@face" && entry.block !== void 0) {
|
|
981
|
+
warnDeprecated(warnings, slot, "`@face { src: \u2026; }`", "use `face: <src> { \u2026 }`");
|
|
982
|
+
let src = "";
|
|
983
|
+
const own = [];
|
|
984
|
+
for (const [k, v] of parseKeyValueBody(entry.block, warnings, "font").entries) {
|
|
985
|
+
if (k === "src") src = stripQuotes(v);
|
|
986
|
+
else own.push([k, v]);
|
|
987
|
+
}
|
|
988
|
+
out.faces.push({ src, entries: own });
|
|
989
|
+
} else if (entry.value) {
|
|
990
|
+
warnings?.push(
|
|
991
|
+
`[RI-1217] @font slot "${slot}" has a stray value "${entry.value.slice(0, 60)}" with no key \u2014 if it continues the previous entry's value, keep the entry on one line. The text was ignored.`
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
if (entry.removal || !entry.key) continue;
|
|
997
|
+
if (!IDENT_KEY_RE.test(entry.key)) {
|
|
998
|
+
warnings?.push(
|
|
999
|
+
`[RI-1035] Invalid @font entry key "${entry.key}" \u2014 keys may only contain letters, numbers, hyphens, and underscores. The entry was skipped.`
|
|
1000
|
+
);
|
|
1001
|
+
continue;
|
|
1002
|
+
}
|
|
1003
|
+
if (entry.block !== void 0 && entry.key !== "face") {
|
|
1004
|
+
warnings?.push(
|
|
1005
|
+
`[RI-1217] @font option "${entry.key}" in slot "${slot}" takes no { \u2026 } block \u2014 the block was ignored.`
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
if (entry.key === "face" && !entry.value) {
|
|
1009
|
+
warnings?.push(
|
|
1010
|
+
`[RI-1217] @font slot "${slot}" has a face: entry with no source \u2014 use \`face: <src> [{ \u2026 }]\`. The entry was ignored.`
|
|
1011
|
+
);
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
if (!entry.value) continue;
|
|
1015
|
+
if (LEGACY_METRICS_KEYS.has(entry.key)) {
|
|
1016
|
+
out.legacyMetrics.push([entry.key, entry.value]);
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
switch (entry.key) {
|
|
1020
|
+
case "face":
|
|
1021
|
+
out.faces.push({
|
|
1022
|
+
src: stripQuotes(entry.value),
|
|
1023
|
+
entries: entry.block !== void 0 ? parseKeyValueBody(entry.block, warnings, "font").entries : []
|
|
1024
|
+
});
|
|
1025
|
+
break;
|
|
1026
|
+
case "features":
|
|
1027
|
+
if (!dropUnsafeValue(entry.key, entry.value, slot, warnings)) out.features = entry.value;
|
|
1028
|
+
break;
|
|
1029
|
+
case "variation":
|
|
1030
|
+
if (!dropUnsafeValue(entry.key, entry.value, slot, warnings)) out.variation = entry.value;
|
|
1031
|
+
break;
|
|
1032
|
+
case "metrics": {
|
|
1033
|
+
const cfg = parseMetricsValue(entry.value, slot, warnings);
|
|
1034
|
+
if (cfg !== void 0) out.metrics = cfg;
|
|
1035
|
+
break;
|
|
1036
|
+
}
|
|
1037
|
+
case "weight":
|
|
1038
|
+
case "style":
|
|
1039
|
+
case "display":
|
|
1040
|
+
case "unicode-range":
|
|
1041
|
+
case "preload":
|
|
1042
|
+
out.faceDefaults.push([entry.key, entry.value]);
|
|
1043
|
+
break;
|
|
1044
|
+
case "italic":
|
|
1045
|
+
warnDeprecated(warnings, slot, "`italic: <src>`", "use `face: <src> { style: italic; }`");
|
|
1046
|
+
out.faces.push({ src: stripQuotes(entry.value), entries: [["style", "italic"]] });
|
|
1047
|
+
break;
|
|
1048
|
+
case "fallback":
|
|
1049
|
+
warnDeprecated(
|
|
1050
|
+
warnings,
|
|
1051
|
+
slot,
|
|
1052
|
+
"`fallback:`",
|
|
1053
|
+
'list fallbacks after the family in the slot preamble (`sans: "Inter", ui-sans-serif from google;`)'
|
|
1054
|
+
);
|
|
1055
|
+
out.fallbackOverride = sanitizeFallbacks(
|
|
1056
|
+
splitTopLevelCommas(entry.value).map((s) => stripQuotes(s.trim()).trim())
|
|
1057
|
+
);
|
|
1058
|
+
break;
|
|
1059
|
+
case "unicodeRange":
|
|
1060
|
+
warnDeprecated(warnings, slot, "`unicodeRange:`", "use the CSS spelling `unicode-range:`");
|
|
1061
|
+
out.faceDefaults.push(["unicode-range", entry.value]);
|
|
1062
|
+
break;
|
|
1063
|
+
case "subset":
|
|
1064
|
+
warnDeprecated(
|
|
1065
|
+
warnings,
|
|
1066
|
+
slot,
|
|
1067
|
+
"`subset:`",
|
|
1068
|
+
"remove the entry (the Google css2 API takes no subset hint)"
|
|
1069
|
+
);
|
|
1070
|
+
break;
|
|
1071
|
+
default:
|
|
1072
|
+
warnings?.push(
|
|
1073
|
+
`[RI-1217] Unknown @font option "${entry.key}" in slot "${slot}" \u2014 the entry was ignored.`
|
|
1074
|
+
);
|
|
1075
|
+
break;
|
|
1076
|
+
}
|
|
764
1077
|
}
|
|
765
|
-
|
|
1078
|
+
foldLegacyMetrics(out, slot, warnings);
|
|
1079
|
+
return out;
|
|
766
1080
|
}
|
|
767
|
-
function
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
1081
|
+
function foldLegacyMetrics(body, slot, warnings) {
|
|
1082
|
+
if (body.legacyMetrics.length === 0) return;
|
|
1083
|
+
warnDeprecated(
|
|
1084
|
+
warnings,
|
|
1085
|
+
slot,
|
|
1086
|
+
"the metricsFallback/sizeAdjust/ascent/descent/lineGap keys",
|
|
1087
|
+
'use the single `metrics:` key (e.g. `metrics: "Arial" 107.64 90.49 22.48 0;`) or omit it for automatic metrics'
|
|
1088
|
+
);
|
|
1089
|
+
if (body.metrics !== void 0) return;
|
|
1090
|
+
const cfg = {};
|
|
1091
|
+
for (const [key, value] of body.legacyMetrics) {
|
|
1092
|
+
if (key === "metricsFallback") {
|
|
1093
|
+
cfg.fallback = stripQuotes(value);
|
|
1094
|
+
} else {
|
|
1095
|
+
const n = Number.parseFloat(value);
|
|
1096
|
+
if (!Number.isNaN(n)) cfg[key] = n;
|
|
1097
|
+
}
|
|
773
1098
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
1099
|
+
const numeric = [cfg.sizeAdjust, cfg.ascent, cfg.descent, cfg.lineGap].filter(
|
|
1100
|
+
(n) => n !== void 0
|
|
1101
|
+
).length;
|
|
1102
|
+
if (numeric === 4 || numeric === 0 && cfg.fallback !== void 0) {
|
|
1103
|
+
body.metrics = cfg;
|
|
1104
|
+
} else if (numeric > 0) {
|
|
1105
|
+
warnings?.push(
|
|
1106
|
+
`[RI-1220] @font slot "${slot}" sets only ${numeric} of the four metric overrides \u2014 size-adjust, ascent, descent, and line-gap are all required. The values were ignored (automatic metrics still apply).`
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
function buildFace(provider, defaults, slot, warnings, own) {
|
|
1111
|
+
const face = createFontFace({ provider });
|
|
1112
|
+
applyFaceOptions(face, defaults, slot, warnings);
|
|
1113
|
+
if (own) applyFaceOptions(face, own, slot, warnings);
|
|
1114
|
+
return face;
|
|
1115
|
+
}
|
|
1116
|
+
function warnDuplicateFaces(slot, warnings) {
|
|
1117
|
+
if (!warnings || slot.faces.length < 2) return;
|
|
1118
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1119
|
+
for (const face of slot.faces) {
|
|
1120
|
+
const key = `${face.weight}|${face.style}`;
|
|
1121
|
+
if (seen.has(key)) {
|
|
1122
|
+
warnings.push(
|
|
1123
|
+
`[RI-1214] @font slot "${slot.slot}" has duplicate faces with weight "${face.weight}" and style "${face.style}" \u2014 the later @font-face wins. Remove the duplicate or give it a distinct weight/style.`
|
|
1124
|
+
);
|
|
781
1125
|
}
|
|
1126
|
+
seen.add(key);
|
|
782
1127
|
}
|
|
783
|
-
|
|
784
|
-
|
|
1128
|
+
}
|
|
1129
|
+
function buildSlot(slot, preambleText, blockBody, warnings) {
|
|
1130
|
+
const pre = parsePreamble(preambleText, slot, warnings);
|
|
1131
|
+
const body = parseSlotBody(blockBody ?? "", slot, warnings);
|
|
1132
|
+
const preloadDefault = body.faceDefaults.some(([k]) => k === "preload");
|
|
1133
|
+
if (pre.system) {
|
|
1134
|
+
if (preloadDefault) {
|
|
1135
|
+
warnings?.push(
|
|
1136
|
+
`[RI-1219] @font slot "${slot}": preload has no effect on system fonts \u2014 only local font files can be preloaded.`
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
return createFontSlot({ slot, family: "", kind: "system" });
|
|
785
1140
|
}
|
|
786
|
-
if (
|
|
787
|
-
|
|
1141
|
+
if (!SAFE_FONT_FAMILY_RE.test(pre.family)) {
|
|
1142
|
+
if (pre.family === "") {
|
|
1143
|
+
warnings?.push(
|
|
1144
|
+
`[RI-1217] @font slot "${slot}" has no font family \u2014 the slot emits an empty font variable.`
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
return createFontSlot({ slot, family: sanitizeFamily(pre.family), kind: "manual" });
|
|
788
1148
|
}
|
|
789
|
-
|
|
790
|
-
|
|
1149
|
+
const srcFaces = pre.legacyFaceSrc ? [{ src: pre.legacyFaceSrc, entries: [] }, ...body.faces] : body.faces;
|
|
1150
|
+
let config;
|
|
1151
|
+
if (pre.google) {
|
|
1152
|
+
if (srcFaces.length > 0) {
|
|
1153
|
+
warnings?.push(
|
|
1154
|
+
`[RI-1204] @font slot "${slot}" loads "${pre.family}" from google but also declares local face entries \u2014 provider fonts can't be combined with local faces. The extra faces were ignored.`
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
config = createFontSlot({
|
|
1158
|
+
slot,
|
|
1159
|
+
family: pre.family,
|
|
1160
|
+
kind: "google",
|
|
1161
|
+
fallback: pre.fallback,
|
|
1162
|
+
faces: [buildFace("google", body.faceDefaults, slot, warnings)]
|
|
1163
|
+
});
|
|
1164
|
+
} else if (srcFaces.length > 0) {
|
|
1165
|
+
config = createFontSlot({
|
|
1166
|
+
slot,
|
|
1167
|
+
family: pre.family,
|
|
1168
|
+
kind: "local",
|
|
1169
|
+
fallback: pre.fallback,
|
|
1170
|
+
faces: srcFaces.map(
|
|
1171
|
+
(f) => buildFace(
|
|
1172
|
+
f.src,
|
|
1173
|
+
body.faceDefaults,
|
|
1174
|
+
slot,
|
|
1175
|
+
warnings,
|
|
1176
|
+
normalizeFaceEntries(f.entries, slot, warnings)
|
|
1177
|
+
)
|
|
1178
|
+
)
|
|
1179
|
+
});
|
|
1180
|
+
warnDuplicateFaces(config, warnings);
|
|
1181
|
+
} else {
|
|
1182
|
+
config = createFontSlot({
|
|
1183
|
+
slot,
|
|
1184
|
+
family: pre.family,
|
|
1185
|
+
kind: "manual",
|
|
1186
|
+
fallback: pre.fallback,
|
|
1187
|
+
faces: [buildFace("", body.faceDefaults, slot, warnings)]
|
|
1188
|
+
});
|
|
791
1189
|
}
|
|
792
|
-
|
|
793
|
-
if (
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
if (
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
);
|
|
801
|
-
}
|
|
802
|
-
const clampedChroma = Math.max(0, Math.min(0.4, chroma));
|
|
803
|
-
const normalizedHue = (hue % 360 + 360) % 360;
|
|
804
|
-
return { type: "generative", chroma: clampedChroma, hue: normalizedHue };
|
|
1190
|
+
if (body.fallbackOverride) config.fallback = body.fallbackOverride;
|
|
1191
|
+
if (body.features !== void 0) config.features = body.features;
|
|
1192
|
+
if (body.variation !== void 0) config.variation = body.variation;
|
|
1193
|
+
if (body.metrics !== void 0) {
|
|
1194
|
+
if (config.kind === "manual") {
|
|
1195
|
+
warnings?.push(
|
|
1196
|
+
`[RI-1220] @font slot "${slot}" sets metrics on a manual font stack \u2014 metrics apply to loaded (google or local file) fonts only. The entry has no effect.`
|
|
1197
|
+
);
|
|
805
1198
|
}
|
|
1199
|
+
config.metrics = body.metrics;
|
|
1200
|
+
}
|
|
1201
|
+
if (config.kind !== "local" && preloadDefault) {
|
|
806
1202
|
warnings?.push(
|
|
807
|
-
`[RI-
|
|
1203
|
+
`[RI-1219] @font slot "${slot}": preload has no effect on ${config.kind} fonts \u2014 only local font files can be preloaded.`
|
|
808
1204
|
);
|
|
809
|
-
return null;
|
|
810
|
-
}
|
|
811
|
-
if (parts.length === 1) {
|
|
812
|
-
const expanded = expandColorSide(value);
|
|
813
|
-
if (expanded !== value) return { type: "explicit", value: expanded };
|
|
814
|
-
if (/^[\w-]+$/.test(value)) return { type: "alias", source: value };
|
|
815
|
-
}
|
|
816
|
-
warnings?.push(
|
|
817
|
-
`[RI-1101] Invalid @color value "${key}: ${value}" \u2014 expected "chroma hue" (e.g., "0.15 30"), a color name alias, or a CSS color function.`
|
|
818
|
-
);
|
|
819
|
-
return null;
|
|
820
|
-
}
|
|
821
|
-
function parseDarkOverrideValue(value) {
|
|
822
|
-
const val = value.trim();
|
|
823
|
-
if (val === "mirror") return { strategy: "mirror" };
|
|
824
|
-
if (val === "fixed") return { strategy: "fixed" };
|
|
825
|
-
if (val.startsWith("shift")) {
|
|
826
|
-
const chromaMatch = val.match(/chroma\s+([+-]?\d+(?:\.\d+)?)/);
|
|
827
|
-
const hueMatch = val.match(/hue\s+([+-]?\d+(?:\.\d+)?)/);
|
|
828
|
-
const chromaDelta = chromaMatch ? Number.parseFloat(chromaMatch[1]) : 0;
|
|
829
|
-
const hueDelta = hueMatch ? Number.parseFloat(hueMatch[1]) : 0;
|
|
830
|
-
return {
|
|
831
|
-
strategy: "shift",
|
|
832
|
-
chromaDelta: Number.isNaN(chromaDelta) ? 0 : chromaDelta,
|
|
833
|
-
hueDelta: Number.isNaN(hueDelta) ? 0 : hueDelta
|
|
834
|
-
};
|
|
835
1205
|
}
|
|
836
|
-
return
|
|
1206
|
+
return config;
|
|
837
1207
|
}
|
|
838
|
-
function
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
}
|
|
1208
|
+
function parseNestedFontBlock(body, warnings) {
|
|
1209
|
+
const cleanedBody = stripCSSComments(body);
|
|
1210
|
+
const configs = [];
|
|
1211
|
+
for (const entry of scanEntries(cleanedBody, { newlineTerminates: false })) {
|
|
1212
|
+
if (entry.removal || entry.fragment) continue;
|
|
1213
|
+
if (entry.unclosedBlock) break;
|
|
1214
|
+
if (!entry.key) continue;
|
|
1215
|
+
if (configs.length >= MAX_FONT_CONFIGS) {
|
|
1216
|
+
warnings?.push(
|
|
1217
|
+
`[RI-1216] @font block exceeds ${MAX_FONT_CONFIGS} slot definitions \u2014 the remaining slots were skipped. Split rarely-used slots into a separate stylesheet or remove unused ones.`
|
|
1218
|
+
);
|
|
1219
|
+
break;
|
|
1220
|
+
}
|
|
1221
|
+
if (entry.block !== void 0) {
|
|
1222
|
+
configs.push(buildSlot(entry.key, entry.value, entry.block, warnings));
|
|
1223
|
+
} else if (entry.value) {
|
|
1224
|
+
configs.push(buildSlot(entry.key, entry.value, void 0, warnings));
|
|
856
1225
|
}
|
|
857
1226
|
}
|
|
858
|
-
return
|
|
1227
|
+
return configs;
|
|
859
1228
|
}
|
|
1229
|
+
|
|
1230
|
+
// src/directives/parsers.ts
|
|
1231
|
+
var MAX_UTILITY_BODY_LENGTH = 1e4;
|
|
1232
|
+
var MAX_CUSTOM_SELECTOR_LENGTH = 2e3;
|
|
1233
|
+
var CUSTOM_VARIANT_NAME_RE = /^[a-z][\w-]*$/;
|
|
860
1234
|
function parseTextBody(body, warnings) {
|
|
861
1235
|
const { entries, removals } = parseKeyValueBody(body, warnings, "text");
|
|
862
1236
|
const text = {};
|
|
@@ -941,186 +1315,6 @@ function parsePreflightDirective(body, modifier, base) {
|
|
|
941
1315
|
}
|
|
942
1316
|
return config;
|
|
943
1317
|
}
|
|
944
|
-
function sanitizeFamily(family) {
|
|
945
|
-
return family.replace(UNSAFE_FONT_FAMILY_CHARS_RE, "");
|
|
946
|
-
}
|
|
947
|
-
function buildFace(provider, faceDefaults, forceStyle) {
|
|
948
|
-
const face = createFontFace({ provider });
|
|
949
|
-
applyFaceOptions(face, faceDefaults);
|
|
950
|
-
if (forceStyle !== void 0) {
|
|
951
|
-
face.style = forceStyle;
|
|
952
|
-
face._styleExplicit = true;
|
|
953
|
-
}
|
|
954
|
-
return face;
|
|
955
|
-
}
|
|
956
|
-
function faceFromBlock(entries, faceDefaults) {
|
|
957
|
-
let src = "";
|
|
958
|
-
const own = [];
|
|
959
|
-
for (const [k, v] of entries) {
|
|
960
|
-
if (k === "src") src = v.replace(/["']/g, "");
|
|
961
|
-
else own.push([k, v]);
|
|
962
|
-
}
|
|
963
|
-
const face = createFontFace({ provider: src });
|
|
964
|
-
applyFaceOptions(face, faceDefaults);
|
|
965
|
-
applyFaceOptions(face, own);
|
|
966
|
-
return face;
|
|
967
|
-
}
|
|
968
|
-
function extractFaceBlocks(body, warnings) {
|
|
969
|
-
const faceBlocks = [];
|
|
970
|
-
let rest = "";
|
|
971
|
-
let i = 0;
|
|
972
|
-
while (i < body.length) {
|
|
973
|
-
const at = body.indexOf("@face", i);
|
|
974
|
-
if (at === -1) {
|
|
975
|
-
rest += body.slice(i);
|
|
976
|
-
break;
|
|
977
|
-
}
|
|
978
|
-
rest += body.slice(i, at);
|
|
979
|
-
let j = at + 5;
|
|
980
|
-
while (j < body.length && /\s/.test(body[j])) j++;
|
|
981
|
-
if (body[j] !== "{") {
|
|
982
|
-
rest += body.slice(at, j);
|
|
983
|
-
i = j;
|
|
984
|
-
continue;
|
|
985
|
-
}
|
|
986
|
-
const close = findClosingBrace(body, j);
|
|
987
|
-
if (close === -1) {
|
|
988
|
-
rest += body.slice(at);
|
|
989
|
-
break;
|
|
990
|
-
}
|
|
991
|
-
faceBlocks.push(parseKeyValueBody(body.slice(j + 1, close), warnings, "font").entries);
|
|
992
|
-
i = close + 1;
|
|
993
|
-
}
|
|
994
|
-
return { faceBlocks, rest };
|
|
995
|
-
}
|
|
996
|
-
function processSlotBlock(blockBody, warnings) {
|
|
997
|
-
const { faceBlocks, rest } = extractFaceBlocks(blockBody, warnings);
|
|
998
|
-
const { entries } = parseKeyValueBody(rest, warnings, "font");
|
|
999
|
-
const faceDefaults = [];
|
|
1000
|
-
const slotOpts = [];
|
|
1001
|
-
const italicSrcs = [];
|
|
1002
|
-
for (const [k, v] of entries) {
|
|
1003
|
-
if (k === "italic") italicSrcs.push(v.replace(/["']/g, ""));
|
|
1004
|
-
else if (FACE_DEFAULT_KEYS.has(k)) faceDefaults.push([k, v]);
|
|
1005
|
-
else slotOpts.push([k, v]);
|
|
1006
|
-
}
|
|
1007
|
-
return { faceDefaults, slotOpts, faceBlocks, italicSrcs };
|
|
1008
|
-
}
|
|
1009
|
-
function buildFaces(primaryProvider, block) {
|
|
1010
|
-
const faces = [];
|
|
1011
|
-
if (primaryProvider !== null) faces.push(buildFace(primaryProvider, block.faceDefaults));
|
|
1012
|
-
for (const fb of block.faceBlocks) faces.push(faceFromBlock(fb, block.faceDefaults));
|
|
1013
|
-
for (const src of block.italicSrcs) faces.push(buildFace(src, block.faceDefaults, "italic"));
|
|
1014
|
-
if (faces.length === 0) faces.push(buildFace("", block.faceDefaults));
|
|
1015
|
-
return faces;
|
|
1016
|
-
}
|
|
1017
|
-
function warnDuplicateFaces(slot, warnings) {
|
|
1018
|
-
if (!warnings || slot.faces.length < 2) return;
|
|
1019
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1020
|
-
for (const face of slot.faces) {
|
|
1021
|
-
const key = `${face.weight}|${face.style}`;
|
|
1022
|
-
if (seen.has(key)) {
|
|
1023
|
-
warnings.push(
|
|
1024
|
-
`[RI-1214] @font slot "${slot.slot}" has duplicate faces with weight "${face.weight}" and style "${face.style}" \u2014 the later @font-face wins. Remove the duplicate or give it a distinct weight/style.`
|
|
1025
|
-
);
|
|
1026
|
-
}
|
|
1027
|
-
seen.add(key);
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
function parseFontBody(body, slot, warnings) {
|
|
1031
|
-
const normalizedBody = stripCSSComments(body).trim();
|
|
1032
|
-
if (normalizedBody === "system") {
|
|
1033
|
-
return createFontSlot({ slot, family: "", kind: "system" });
|
|
1034
|
-
}
|
|
1035
|
-
const braceIdx = topLevelIndexOf(normalizedBody, "{");
|
|
1036
|
-
const matchable = braceIdx === -1 ? normalizedBody.replace(/\s+/g, " ") : normalizedBody.slice(0, braceIdx).replace(/\s+/g, " ") + normalizedBody.slice(braceIdx);
|
|
1037
|
-
const fromMatch = matchable.match(/^(["'])(.+?)\1\s+from\s+(.+?)(?:\s*\{([\s\S]*)\})?$/);
|
|
1038
|
-
if (fromMatch) {
|
|
1039
|
-
const family = fromMatch[2];
|
|
1040
|
-
if (!SAFE_FONT_FAMILY_RE.test(family)) {
|
|
1041
|
-
return createFontSlot({ slot, family: sanitizeFamily(family), kind: "manual" });
|
|
1042
|
-
}
|
|
1043
|
-
const provider = fromMatch[3].trim().replace(/["']/g, "");
|
|
1044
|
-
const kind = kindFromProvider(provider);
|
|
1045
|
-
const block = processSlotBlock(fromMatch[4] || "", warnings);
|
|
1046
|
-
if (kind === "google" || kind === "system") {
|
|
1047
|
-
if (block.faceBlocks.length > 0 || block.italicSrcs.length > 0) {
|
|
1048
|
-
warnings?.push(
|
|
1049
|
-
`[RI-1204] @font slot "${slot}" loads "${family}" from "${provider}" but also declares @face/italic faces \u2014 provider fonts can't be combined with local faces. The extra faces were ignored.`
|
|
1050
|
-
);
|
|
1051
|
-
}
|
|
1052
|
-
const config2 = createFontSlot({
|
|
1053
|
-
slot,
|
|
1054
|
-
family,
|
|
1055
|
-
kind,
|
|
1056
|
-
faces: [buildFace(provider, block.faceDefaults)]
|
|
1057
|
-
});
|
|
1058
|
-
applySlotOptions(config2, block.slotOpts);
|
|
1059
|
-
return config2;
|
|
1060
|
-
}
|
|
1061
|
-
const config = createFontSlot({ slot, family, kind, faces: buildFaces(provider, block) });
|
|
1062
|
-
applySlotOptions(config, block.slotOpts);
|
|
1063
|
-
warnDuplicateFaces(config, warnings);
|
|
1064
|
-
return config;
|
|
1065
|
-
}
|
|
1066
|
-
const manualMatch = matchable.match(/^(.+?)(?:\s*\{([\s\S]*)\})?$/);
|
|
1067
|
-
if (manualMatch) {
|
|
1068
|
-
const stackParts = manualMatch[1].trim().split(",").map((s) => s.trim());
|
|
1069
|
-
const family = stackParts[0].replace(/["']/g, "");
|
|
1070
|
-
if (!SAFE_FONT_FAMILY_RE.test(family)) {
|
|
1071
|
-
return createFontSlot({ slot, family: sanitizeFamily(family), kind: "manual" });
|
|
1072
|
-
}
|
|
1073
|
-
const fallback = stackParts.slice(1).map((s) => s.replace(/["']/g, "").trim());
|
|
1074
|
-
const block = processSlotBlock(manualMatch[2] || "", warnings);
|
|
1075
|
-
if (block.faceBlocks.length > 0 || block.italicSrcs.length > 0) {
|
|
1076
|
-
const config2 = createFontSlot({
|
|
1077
|
-
slot,
|
|
1078
|
-
family,
|
|
1079
|
-
kind: "local",
|
|
1080
|
-
fallback,
|
|
1081
|
-
faces: buildFaces(null, block)
|
|
1082
|
-
});
|
|
1083
|
-
applySlotOptions(config2, block.slotOpts);
|
|
1084
|
-
warnDuplicateFaces(config2, warnings);
|
|
1085
|
-
return config2;
|
|
1086
|
-
}
|
|
1087
|
-
const config = createFontSlot({
|
|
1088
|
-
slot,
|
|
1089
|
-
family,
|
|
1090
|
-
kind: "manual",
|
|
1091
|
-
fallback,
|
|
1092
|
-
faces: [buildFace("", block.faceDefaults)]
|
|
1093
|
-
});
|
|
1094
|
-
applySlotOptions(config, block.slotOpts);
|
|
1095
|
-
return config;
|
|
1096
|
-
}
|
|
1097
|
-
return createFontSlot({
|
|
1098
|
-
slot,
|
|
1099
|
-
family: sanitizeFamily(normalizedBody.replace(/["']/g, "")),
|
|
1100
|
-
kind: "manual"
|
|
1101
|
-
});
|
|
1102
|
-
}
|
|
1103
|
-
function parseNestedFontBlock(body, warnings) {
|
|
1104
|
-
const cleanedBody = stripCSSComments(body);
|
|
1105
|
-
const configs = [];
|
|
1106
|
-
for (const entry of scanEntries(cleanedBody, { newlineTerminates: false })) {
|
|
1107
|
-
if (entry.removal || entry.fragment) continue;
|
|
1108
|
-
if (entry.unclosedBlock) break;
|
|
1109
|
-
if (!entry.key) continue;
|
|
1110
|
-
if (configs.length >= MAX_FONT_CONFIGS) {
|
|
1111
|
-
warnings?.push(
|
|
1112
|
-
`[RI-1216] @font block exceeds ${MAX_FONT_CONFIGS} slot definitions \u2014 the remaining slots were skipped. Split rarely-used slots into a separate stylesheet or remove unused ones.`
|
|
1113
|
-
);
|
|
1114
|
-
break;
|
|
1115
|
-
}
|
|
1116
|
-
if (entry.block !== void 0) {
|
|
1117
|
-
configs.push(parseFontBody(`${entry.value} { ${entry.block} }`, entry.key, warnings));
|
|
1118
|
-
} else if (entry.value) {
|
|
1119
|
-
configs.push(parseFontBody(entry.value, entry.key, warnings));
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
return configs;
|
|
1123
|
-
}
|
|
1124
1318
|
function stripLeadingClassDot(name) {
|
|
1125
1319
|
return name.startsWith(".") ? name.slice(1) : name;
|
|
1126
1320
|
}
|
|
@@ -2996,6 +3190,17 @@ function parseCustomUtilityBody(body) {
|
|
|
2996
3190
|
flushStatement(body.length);
|
|
2997
3191
|
return root;
|
|
2998
3192
|
}
|
|
3193
|
+
var parsedBodyCache = /* @__PURE__ */ new Map();
|
|
3194
|
+
var PARSED_BODY_CACHE_MAX = 500;
|
|
3195
|
+
function getParsedBody(body) {
|
|
3196
|
+
let tree = parsedBodyCache.get(body);
|
|
3197
|
+
if (!tree) {
|
|
3198
|
+
if (parsedBodyCache.size >= PARSED_BODY_CACHE_MAX) parsedBodyCache.clear();
|
|
3199
|
+
tree = parseCustomUtilityBody(body);
|
|
3200
|
+
parsedBodyCache.set(body, tree);
|
|
3201
|
+
}
|
|
3202
|
+
return tree;
|
|
3203
|
+
}
|
|
2999
3204
|
var MAX_CUSTOM_APPLY_DEPTH = 5;
|
|
3000
3205
|
var APPLY_CLASS_SPLIT_RE = /\s+/;
|
|
3001
3206
|
function forEachApplyClass(body, visit) {
|
|
@@ -3039,7 +3244,7 @@ function resolveCustomUtility(utility, value, theme, resolve, visiting) {
|
|
|
3039
3244
|
const map = getCustomUtilityMap(theme);
|
|
3040
3245
|
const cu = value === null ? map.get(utility) : map.get(`${utility}-${value}`);
|
|
3041
3246
|
if (!cu || cu.functional) return null;
|
|
3042
|
-
const tree =
|
|
3247
|
+
const tree = getParsedBody(cu.body);
|
|
3043
3248
|
let expansion = null;
|
|
3044
3249
|
if (hasApplyLikeDirective(cu.body)) {
|
|
3045
3250
|
const visited = visiting ?? /* @__PURE__ */ new Set();
|
|
@@ -3063,7 +3268,7 @@ function resolveCustomUtility(utility, value, theme, resolve, visiting) {
|
|
|
3063
3268
|
return { declarations, nested };
|
|
3064
3269
|
}
|
|
3065
3270
|
function extractCustomUtilityRootInfo(body) {
|
|
3066
|
-
const tree =
|
|
3271
|
+
const tree = getParsedBody(body);
|
|
3067
3272
|
const seen = /* @__PURE__ */ new Set();
|
|
3068
3273
|
const properties = [];
|
|
3069
3274
|
for (const d of tree.declarations) {
|
|
@@ -3072,7 +3277,7 @@ function extractCustomUtilityRootInfo(body) {
|
|
|
3072
3277
|
properties.push(d.property);
|
|
3073
3278
|
}
|
|
3074
3279
|
}
|
|
3075
|
-
return { properties, applyClasses: tree.applyClasses };
|
|
3280
|
+
return { properties, applyClasses: [...tree.applyClasses] };
|
|
3076
3281
|
}
|
|
3077
3282
|
|
|
3078
3283
|
// src/utilities/helpers.ts
|
|
@@ -8424,6 +8629,48 @@ function createEmptyCompilationResult() {
|
|
|
8424
8629
|
warnings: []
|
|
8425
8630
|
};
|
|
8426
8631
|
}
|
|
8632
|
+
var classCompileMemo = /* @__PURE__ */ new WeakMap();
|
|
8633
|
+
function compileClassEntry(raw, theme, customVariantMap, breakpointWeights, variantMemo) {
|
|
8634
|
+
const scratch = createEmptyCompilationResult();
|
|
8635
|
+
const parsed = parseUtility(raw);
|
|
8636
|
+
const rule = compileUtility(
|
|
8637
|
+
parsed,
|
|
8638
|
+
theme,
|
|
8639
|
+
scratch,
|
|
8640
|
+
customVariantMap,
|
|
8641
|
+
/* @__PURE__ */ new Set(),
|
|
8642
|
+
breakpointWeights,
|
|
8643
|
+
variantMemo
|
|
8644
|
+
);
|
|
8645
|
+
const support = new Array(SUPPORT_BLOCKS.length).fill(false);
|
|
8646
|
+
if (rule) {
|
|
8647
|
+
for (let i = 0; i < SUPPORT_BLOCKS.length; i++) {
|
|
8648
|
+
const block = SUPPORT_BLOCKS[i];
|
|
8649
|
+
if (block.test(rule.css) || block.utilities?.includes(parsed.utility)) {
|
|
8650
|
+
support[i] = true;
|
|
8651
|
+
}
|
|
8652
|
+
}
|
|
8653
|
+
} else if (parsed.arbitrary && parsed.value) {
|
|
8654
|
+
const bracketContent = parsed.value.replace(/^\[|\]$/g, "");
|
|
8655
|
+
if (shouldWarnUnresolvedArbitrary(bracketContent)) {
|
|
8656
|
+
const truncated = parsed.raw.length > 100 ? `${parsed.raw.slice(0, 100)}...` : parsed.raw;
|
|
8657
|
+
scratch.warnings.push(
|
|
8658
|
+
`[RI-1002] Could not resolve arbitrary utility "${truncated}". Arbitrary utilities use \`[property:value]\` syntax \u2014 e.g. \`[padding:1rem]\` or \`[mask-type:luminance]\`. Check that the property name is a known CSS property and the value is well-formed (no stray spaces, quoted strings escaped). If you meant to set a CSS variable, use \`[--my-var:value]\`.`
|
|
8659
|
+
);
|
|
8660
|
+
}
|
|
8661
|
+
}
|
|
8662
|
+
return {
|
|
8663
|
+
rule,
|
|
8664
|
+
warnings: scratch.warnings,
|
|
8665
|
+
usedColorStops: scratch.usedColorStops,
|
|
8666
|
+
usedTextSizes: scratch.usedTextSizes,
|
|
8667
|
+
usedFonts: scratch.usedFonts,
|
|
8668
|
+
usedRounded: scratch.usedRounded,
|
|
8669
|
+
usedShadows: scratch.usedShadows,
|
|
8670
|
+
usedAnimations: scratch.usedAnimations,
|
|
8671
|
+
support
|
|
8672
|
+
};
|
|
8673
|
+
}
|
|
8427
8674
|
function shouldWarnUnresolvedArbitrary(bracketContent) {
|
|
8428
8675
|
if (/[=*]/.test(bracketContent)) return false;
|
|
8429
8676
|
if (/\s/.test(bracketContent)) return false;
|
|
@@ -8476,42 +8723,39 @@ function compileInternal(classNames, theme, variantMapCache) {
|
|
|
8476
8723
|
const variantMemo = /* @__PURE__ */ new Map();
|
|
8477
8724
|
const seen = /* @__PURE__ */ new Set();
|
|
8478
8725
|
const supportNeeded = new Array(SUPPORT_BLOCKS.length).fill(false);
|
|
8726
|
+
let classMemo = classCompileMemo.get(theme);
|
|
8727
|
+
if (!classMemo) {
|
|
8728
|
+
classMemo = /* @__PURE__ */ new Map();
|
|
8729
|
+
classCompileMemo.set(theme, classMemo);
|
|
8730
|
+
}
|
|
8479
8731
|
for (const raw of classNames) {
|
|
8480
8732
|
if (seen.has(raw)) continue;
|
|
8481
8733
|
seen.add(raw);
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
warnSeen
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
if (shouldWarnUnresolvedArbitrary(bracketContent)) {
|
|
8496
|
-
const truncated = parsed.raw.length > 100 ? `${parsed.raw.slice(0, 100)}...` : parsed.raw;
|
|
8497
|
-
pushWarningsDeduped(
|
|
8498
|
-
result.warnings,
|
|
8499
|
-
[
|
|
8500
|
-
`[RI-1002] Could not resolve arbitrary utility "${truncated}". Arbitrary utilities use \`[property:value]\` syntax \u2014 e.g. \`[padding:1rem]\` or \`[mask-type:luminance]\`. Check that the property name is a known CSS property and the value is well-formed (no stray spaces, quoted strings escaped). If you meant to set a CSS variable, use \`[--my-var:value]\`.`
|
|
8501
|
-
],
|
|
8502
|
-
warnSeen
|
|
8503
|
-
);
|
|
8504
|
-
}
|
|
8734
|
+
let entry = classMemo.get(raw);
|
|
8735
|
+
if (!entry) {
|
|
8736
|
+
entry = compileClassEntry(raw, theme, customVariantMap, breakpointWeights, variantMemo);
|
|
8737
|
+
classMemo.set(raw, entry);
|
|
8738
|
+
}
|
|
8739
|
+
if (entry.warnings.length > 0) {
|
|
8740
|
+
pushWarningsDeduped(result.warnings, entry.warnings, warnSeen);
|
|
8741
|
+
}
|
|
8742
|
+
for (const [hue, stops] of entry.usedColorStops) {
|
|
8743
|
+
let set = result.usedColorStops.get(hue);
|
|
8744
|
+
if (!set) {
|
|
8745
|
+
set = /* @__PURE__ */ new Set();
|
|
8746
|
+
result.usedColorStops.set(hue, set);
|
|
8505
8747
|
}
|
|
8506
|
-
|
|
8507
|
-
}
|
|
8508
|
-
result.
|
|
8748
|
+
for (const stop of stops) set.add(stop);
|
|
8749
|
+
}
|
|
8750
|
+
for (const v of entry.usedTextSizes) result.usedTextSizes.add(v);
|
|
8751
|
+
for (const v of entry.usedFonts) result.usedFonts.add(v);
|
|
8752
|
+
for (const v of entry.usedRounded) result.usedRounded.add(v);
|
|
8753
|
+
for (const v of entry.usedShadows) result.usedShadows.add(v);
|
|
8754
|
+
for (const v of entry.usedAnimations) result.usedAnimations.add(v);
|
|
8755
|
+
if (!entry.rule) continue;
|
|
8756
|
+
result.rules.push(entry.rule);
|
|
8509
8757
|
for (let i = 0; i < SUPPORT_BLOCKS.length; i++) {
|
|
8510
|
-
if (
|
|
8511
|
-
const block = SUPPORT_BLOCKS[i];
|
|
8512
|
-
if (block.test(compiled.css) || block.utilities?.includes(parsed.utility)) {
|
|
8513
|
-
supportNeeded[i] = true;
|
|
8514
|
-
}
|
|
8758
|
+
if (entry.support[i]) supportNeeded[i] = true;
|
|
8515
8759
|
}
|
|
8516
8760
|
}
|
|
8517
8761
|
result.rules.sort(
|
|
@@ -9087,6 +9331,8 @@ var CandidateCollector = class {
|
|
|
9087
9331
|
origin = "plain";
|
|
9088
9332
|
helperName = null;
|
|
9089
9333
|
byCandidate = /* @__PURE__ */ new Map();
|
|
9334
|
+
/** value -> byCandidate keys, so delete() is O(occurrences of the value). */
|
|
9335
|
+
keysByValue = /* @__PURE__ */ new Map();
|
|
9090
9336
|
contexts = [];
|
|
9091
9337
|
setOrigin(origin) {
|
|
9092
9338
|
this.origin = origin;
|
|
@@ -9111,22 +9357,45 @@ var CandidateCollector = class {
|
|
|
9111
9357
|
const candidate = { value, start, end, origin: "plain" };
|
|
9112
9358
|
if (prefixStart >= 0) candidate.groupPrefix = { start: prefixStart, end: prefixEnd };
|
|
9113
9359
|
this.byCandidate.set(key, candidate);
|
|
9360
|
+
let keys = this.keysByValue.get(value);
|
|
9361
|
+
if (!keys) {
|
|
9362
|
+
keys = /* @__PURE__ */ new Set();
|
|
9363
|
+
this.keysByValue.set(value, keys);
|
|
9364
|
+
}
|
|
9365
|
+
keys.add(key);
|
|
9114
9366
|
}
|
|
9115
9367
|
delete(value) {
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9368
|
+
const keys = this.keysByValue.get(value);
|
|
9369
|
+
if (!keys) return;
|
|
9370
|
+
for (const key of keys) this.byCandidate.delete(key);
|
|
9371
|
+
this.keysByValue.delete(value);
|
|
9119
9372
|
}
|
|
9120
9373
|
finish() {
|
|
9121
9374
|
const candidates = [...this.byCandidate.values()].sort(
|
|
9122
9375
|
(a, b) => a.start - b.start || a.end - b.end
|
|
9123
9376
|
);
|
|
9124
9377
|
if (this.contexts.length > 0) {
|
|
9378
|
+
const byStart = [...this.contexts].sort((a, b) => a.start - b.start);
|
|
9379
|
+
const live = [];
|
|
9380
|
+
let next = 0;
|
|
9125
9381
|
for (const candidate of candidates) {
|
|
9382
|
+
while (next < byStart.length && byStart[next].start <= candidate.start) {
|
|
9383
|
+
live.push(byStart[next]);
|
|
9384
|
+
next++;
|
|
9385
|
+
}
|
|
9126
9386
|
let best = null;
|
|
9127
|
-
for (
|
|
9128
|
-
|
|
9129
|
-
|
|
9387
|
+
for (let i = 0; i < live.length; i++) {
|
|
9388
|
+
const context = live[i];
|
|
9389
|
+
if (context.end < candidate.start) {
|
|
9390
|
+
live[i] = live[live.length - 1];
|
|
9391
|
+
live.pop();
|
|
9392
|
+
i--;
|
|
9393
|
+
continue;
|
|
9394
|
+
}
|
|
9395
|
+
if (candidate.end <= context.end) {
|
|
9396
|
+
const width = context.end - context.start;
|
|
9397
|
+
const bestWidth = best ? best.end - best.start : -1;
|
|
9398
|
+
if (!best || width < bestWidth || width === bestWidth && context.id < best.id) {
|
|
9130
9399
|
best = context;
|
|
9131
9400
|
}
|
|
9132
9401
|
}
|
|
@@ -9716,15 +9985,15 @@ function analyzeProjectCSS(css) {
|
|
|
9716
9985
|
}
|
|
9717
9986
|
|
|
9718
9987
|
export {
|
|
9988
|
+
isRIDebug,
|
|
9989
|
+
withTimeout,
|
|
9990
|
+
isAtRuleBoundary,
|
|
9991
|
+
codepointCompare,
|
|
9719
9992
|
DIRECTIVE_TYPE_NAMES,
|
|
9720
9993
|
findClosingBrace,
|
|
9721
9994
|
APPLY_ALIASES,
|
|
9722
9995
|
hasApplyLikeDirective,
|
|
9723
9996
|
APPLY_LIKE_MATCH_RE,
|
|
9724
|
-
isRIDebug,
|
|
9725
|
-
withTimeout,
|
|
9726
|
-
isAtRuleBoundary,
|
|
9727
|
-
codepointCompare,
|
|
9728
9997
|
DIRECTIVE_NAMES_SET,
|
|
9729
9998
|
directiveAtRulePattern,
|
|
9730
9999
|
isAtRuleNameChar,
|