@master/css-binding 0.0.0-site.1793659883.g69c9aebcf628
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/artifacts/mastercss.node +0 -0
- package/artifacts/mcss +0 -0
- package/dist/binding-options.d.ts +14 -0
- package/dist/binding-options.js +15 -0
- package/dist/binding.d.ts +12 -0
- package/dist/binding.js +23 -0
- package/dist/browser.d.ts +3 -0
- package/dist/browser.js +3 -0
- package/dist/compiler-binding-adapter.js +55 -0
- package/dist/compiler-binding-browser.d.ts +6 -0
- package/dist/compiler-binding-browser.js +24 -0
- package/dist/compiler-binding-contract.d.ts +45 -0
- package/dist/compiler-binding-node.d.ts +5 -0
- package/dist/compiler-binding-node.js +20 -0
- package/dist/compiler-binding-wasm.js +58 -0
- package/dist/compiler-binding.d.ts +6 -0
- package/dist/compiler-binding.js +42 -0
- package/dist/compiler.js +52 -0
- package/dist/engine-binding-adapter.js +61 -0
- package/dist/engine-binding-browser.d.ts +8 -0
- package/dist/engine-binding-browser.js +35 -0
- package/dist/engine-binding-js.js +81 -0
- package/dist/engine-binding-node.d.ts +9 -0
- package/dist/engine-binding-node.js +38 -0
- package/dist/engine-binding-wasm.js +64 -0
- package/dist/engine-binding.d.ts +40 -0
- package/dist/engine-binding.js +57 -0
- package/dist/engine-contract.d.ts +14 -0
- package/dist/engine-js/src/animation.js +154 -0
- package/dist/engine-js/src/apply.js +207 -0
- package/dist/engine-js/src/class-syntax.js +333 -0
- package/dist/engine-js/src/condition.js +440 -0
- package/dist/engine-js/src/css-syntax.js +247 -0
- package/dist/engine-js/src/generate.js +288 -0
- package/dist/engine-js/src/mixin.js +328 -0
- package/dist/engine-js/src/native-properties.js +5 -0
- package/dist/engine-js/src/order.js +97 -0
- package/dist/engine-js/src/program-model.js +19 -0
- package/dist/engine-js/src/program.js +434 -0
- package/dist/engine-js/src/selector.js +195 -0
- package/dist/engine-js/src/session.js +540 -0
- package/dist/engine-js/src/state.js +62 -0
- package/dist/engine-js/src/text.js +51 -0
- package/dist/engine-js/src/utility.js +236 -0
- package/dist/engine-js/src/value.js +254 -0
- package/dist/engine.js +86 -0
- package/dist/errors.js +10 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/native-loader.js +134 -0
- package/dist/normalize-error.js +63 -0
- package/dist/protocol.d.ts +1119 -0
- package/dist/protocol.js +14 -0
- package/dist/tooling-binding-adapter.js +23 -0
- package/dist/tooling-binding-browser.d.ts +5 -0
- package/dist/tooling-binding-browser.js +15 -0
- package/dist/tooling-binding-contract.d.ts +92 -0
- package/dist/tooling-binding-native.js +46 -0
- package/dist/tooling-binding-node.d.ts +4 -0
- package/dist/tooling-binding-node.js +11 -0
- package/dist/tooling-binding-wasm.js +127 -0
- package/dist/tooling-binding.d.ts +5 -0
- package/dist/tooling-binding.js +21 -0
- package/dist/tooling.js +144 -0
- package/dist/version.js +3 -0
- package/package.json +78 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import { asciiLowercase, isAsciiCase, isWhitespace, trim } from "./text.js";
|
|
2
|
+
import { isNameCodePoint, tokenize } from "./css-syntax.js";
|
|
3
|
+
import { decimalIsFinite, decimalToNumber, parseDecimal } from "./value.js";
|
|
4
|
+
import { decodeNativeContent } from "./class-syntax.js";
|
|
5
|
+
import { naturalCompare } from "./order.js";
|
|
6
|
+
// ../engine-js/src/condition.ts
|
|
7
|
+
const isDelim = (token, value) => token?.kind === 8 && token.value === value;
|
|
8
|
+
const QUERY_KEYWORDS = [
|
|
9
|
+
"and",
|
|
10
|
+
"or",
|
|
11
|
+
"not",
|
|
12
|
+
"only"
|
|
13
|
+
];
|
|
14
|
+
const isKeyword = (name) => QUERY_KEYWORDS.some((keyword) => isAsciiCase(name, keyword));
|
|
15
|
+
/**
|
|
16
|
+
* A class query names one condition without the native group parentheses: `media(width>=50rem)`,
|
|
17
|
+
* `media(print)`, `supports(display:grid)`, `container(card|width>=40rem)`. The prelude is the
|
|
18
|
+
* native query the engine emits, with those parentheses restored.
|
|
19
|
+
*/
|
|
20
|
+
function parseNativeQuery(token) {
|
|
21
|
+
const parts = queryParts(token);
|
|
22
|
+
if (!parts) return;
|
|
23
|
+
const decoded = decodeNativeContent(parts[1]);
|
|
24
|
+
const prelude = decoded === void 0 ? void 0 : nativePrelude(parts[0], decoded);
|
|
25
|
+
return prelude === void 0 ? void 0 : {
|
|
26
|
+
kind: parts[0],
|
|
27
|
+
prelude
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** A balanced recognized query beyond the class grammar, which belongs in CSS. */
|
|
31
|
+
function queryRequiresCss(token) {
|
|
32
|
+
const parts = queryParts(token);
|
|
33
|
+
if (!parts) return false;
|
|
34
|
+
const decoded = decodeNativeContent(parts[1]);
|
|
35
|
+
return decoded !== void 0 && nativePrelude(parts[0], decoded) === void 0 && legacyQuery(token) === void 0;
|
|
36
|
+
}
|
|
37
|
+
/** The class form of a query written with its native group parentheses: `media((width>=50rem))` → `media(width>=50rem)`. */
|
|
38
|
+
function legacyQuery(token) {
|
|
39
|
+
const parts = queryParts(token);
|
|
40
|
+
if (!parts) return;
|
|
41
|
+
const [kind, body] = parts;
|
|
42
|
+
const open = body.indexOf("(");
|
|
43
|
+
if (open < 0 || !(open === 0 || body.slice(0, open).endsWith("|")) || !body.endsWith(")")) return;
|
|
44
|
+
let depth = 0;
|
|
45
|
+
for (let index = open; index < body.length; index++) if (body[index] === "(") depth++;
|
|
46
|
+
else if (body[index] === ")" && --depth === 0 && index !== body.length - 1) return;
|
|
47
|
+
const candidate = `${kind}(${body.slice(0, open)}${body.slice(open + 1, -1)})`;
|
|
48
|
+
return parseNativeQuery(candidate) ? candidate : void 0;
|
|
49
|
+
}
|
|
50
|
+
function queryParts(token) {
|
|
51
|
+
const open = token.indexOf("(");
|
|
52
|
+
const kind = token.slice(0, open);
|
|
53
|
+
if (open < 0 || kind !== "media" && kind !== "supports" && kind !== "container" || !token.endsWith(")")) return;
|
|
54
|
+
const body = token.slice(open + 1, -1);
|
|
55
|
+
return trim(body) ? [kind, body] : void 0;
|
|
56
|
+
}
|
|
57
|
+
/** Media Queries Level 4 media types, including the deprecated ones that match nothing. */
|
|
58
|
+
const MEDIA_TYPES = [
|
|
59
|
+
"all",
|
|
60
|
+
"print",
|
|
61
|
+
"screen",
|
|
62
|
+
"tty",
|
|
63
|
+
"tv",
|
|
64
|
+
"projection",
|
|
65
|
+
"handheld",
|
|
66
|
+
"braille",
|
|
67
|
+
"embossed",
|
|
68
|
+
"aural",
|
|
69
|
+
"speech"
|
|
70
|
+
];
|
|
71
|
+
function nativePrelude(kind, prelude) {
|
|
72
|
+
const tokens = tokenize(prelude);
|
|
73
|
+
const isName = (token) => token?.kind === 0 && !isKeyword(token.value);
|
|
74
|
+
if (!tokens.length || tokens[0].kind === 0 && isKeyword(tokens[0].value)) return;
|
|
75
|
+
const start = kind === "container" && isName(tokens[0]) && tokens.length > 1 && tokens[1].kind !== 8 ? 1 : 0;
|
|
76
|
+
const condition = trim(prelude.slice(tokens[start].start));
|
|
77
|
+
const containerName = trim(prelude.slice(0, tokens[start].start));
|
|
78
|
+
const wrap = (text) => containerName ? `${containerName} ${text}` : text;
|
|
79
|
+
if (kind === "container" && tokens[start].kind === 3 && isAsciiCase(tokens[start].value, "style")) {
|
|
80
|
+
const end = tokens.length - 1;
|
|
81
|
+
const inner = tokens.slice(start + 1, end);
|
|
82
|
+
return tokens[start].close === end && inner[0]?.kind === 0 && inner[0].value.startsWith("--") && (inner.length === 1 || inner.length > 2 && isDelim(inner[1], ":") && simpleValue(tokens, start + 3, end)) ? wrap(condition) : void 0;
|
|
83
|
+
}
|
|
84
|
+
if (kind === "media" && tokens.length === 1) {
|
|
85
|
+
if (tokens[0].kind !== 0) return;
|
|
86
|
+
return MEDIA_TYPES.some((type) => isAsciiCase(tokens[0].value, type)) ? condition : `(${condition})`;
|
|
87
|
+
}
|
|
88
|
+
return simpleCondition(kind, tokens, start) ? wrap(`(${condition})`) : void 0;
|
|
89
|
+
}
|
|
90
|
+
/** One feature, declaration or range from `start` to the end, without its group parentheses. */
|
|
91
|
+
function simpleCondition(kind, tokens, start) {
|
|
92
|
+
const end = tokens.length;
|
|
93
|
+
const inner = tokens.slice(start);
|
|
94
|
+
if (!inner.length) return false;
|
|
95
|
+
const isName = (token) => token?.kind === 0 && !isKeyword(token.value);
|
|
96
|
+
const ident = isName(inner[0]);
|
|
97
|
+
const declaration = ident && inner.length > 2 && isDelim(inner[1], ":") && simpleValue(tokens, start + 2, end);
|
|
98
|
+
if (kind === "supports") return declaration;
|
|
99
|
+
if (ident && inner.length === 1) return true;
|
|
100
|
+
if (ident && inner.length > 2 && isDelim(inner[1], ":")) return declaration;
|
|
101
|
+
const operands = [];
|
|
102
|
+
const comparisons = [];
|
|
103
|
+
let index = start;
|
|
104
|
+
while (index < end) {
|
|
105
|
+
const token = tokens[index];
|
|
106
|
+
const name = isName(token);
|
|
107
|
+
if (!name && token.kind !== 4 && token.kind !== 5 && token.kind !== 6 && token.kind !== 3) return false;
|
|
108
|
+
operands.push(name);
|
|
109
|
+
index = token.close < 0 ? index + 1 : token.close + 1;
|
|
110
|
+
if (index + 1 < end && isDelim(tokens[index], "/") && tokens[index + 1].kind === 4) index += 2;
|
|
111
|
+
if (index === end) break;
|
|
112
|
+
const operator = tokens[index];
|
|
113
|
+
if (operator.kind !== 8 || !"<>=".includes(operator.value)) return false;
|
|
114
|
+
index++;
|
|
115
|
+
if (operator.value !== "=" && index < end && isDelim(tokens[index], "=")) index++;
|
|
116
|
+
comparisons.push(operator.value);
|
|
117
|
+
if (index === end) return false;
|
|
118
|
+
}
|
|
119
|
+
if (comparisons.length === 1) return operands.length === 2 && operands.filter(Boolean).length === 1;
|
|
120
|
+
return comparisons.length === 2 && comparisons[0] === comparisons[1] && comparisons[0] !== "=" && operands.length === 3 && !operands[0] && operands[1] && !operands[2];
|
|
121
|
+
}
|
|
122
|
+
/** A feature value without query structure: functions only, no bare groups or separators. */
|
|
123
|
+
function simpleValue(tokens, index, end) {
|
|
124
|
+
while (index < end) {
|
|
125
|
+
const token = tokens[index];
|
|
126
|
+
if (token.kind === 8 && "|;{}".includes(token.value)) return false;
|
|
127
|
+
if (token.close >= 0) {
|
|
128
|
+
if (token.kind !== 3) return false;
|
|
129
|
+
index = token.close + 1;
|
|
130
|
+
} else index++;
|
|
131
|
+
}
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
function resolveLayerCondition(token) {
|
|
135
|
+
if (!token.startsWith("layer(") || !token.endsWith(")")) return;
|
|
136
|
+
const name = token.slice(6, -1);
|
|
137
|
+
return name === "base" || name === "defaults" || name === "components" || name === "utilities" ? name : void 0;
|
|
138
|
+
}
|
|
139
|
+
/** A condition's wrapper rule and its kind, for conditions that are not custom media or layers. */
|
|
140
|
+
function renderConditionToken(token, program) {
|
|
141
|
+
if (token === "starting-style") return ["starting-style", "@starting-style"];
|
|
142
|
+
const query = parseNativeQuery(token);
|
|
143
|
+
if (!query) return;
|
|
144
|
+
let prelude = query.prelude;
|
|
145
|
+
const tokens = tokenize(prelude);
|
|
146
|
+
const open = tokens.length - 3;
|
|
147
|
+
const name = tokens[open + 1];
|
|
148
|
+
if (query.kind === "container" && isDelim(tokens[open], "(") && tokens[open].close === open + 2 && name.kind === 0 && name.value.startsWith("--") && name.value.length > 2) {
|
|
149
|
+
const condition = program.customContainers.get(name.value);
|
|
150
|
+
prelude = condition === void 0 ? condition : prelude.slice(0, tokens[open].start) + condition;
|
|
151
|
+
}
|
|
152
|
+
return prelude === void 0 ? void 0 : [query.kind, `@${query.kind} ${prelude}`];
|
|
153
|
+
}
|
|
154
|
+
const RANGE_FEATURES = /* @__PURE__ */ new Set([
|
|
155
|
+
"width",
|
|
156
|
+
"height",
|
|
157
|
+
"resolution",
|
|
158
|
+
"aspect-ratio"
|
|
159
|
+
]);
|
|
160
|
+
function rangeDimension(value) {
|
|
161
|
+
const end = value.search(/[^0-9.+-]/);
|
|
162
|
+
const decimal = parseDecimal(end < 0 ? value : value.slice(0, end));
|
|
163
|
+
const unit = end < 0 ? "" : value.slice(end);
|
|
164
|
+
if (!decimal || !decimalIsFinite(decimal) || !/^[a-zA-Z%]*$/.test(unit)) return;
|
|
165
|
+
return [decimalToNumber(decimal), asciiLowercase(unit)];
|
|
166
|
+
}
|
|
167
|
+
function intersectRange(target, source) {
|
|
168
|
+
const lower = source.lower;
|
|
169
|
+
if (lower) {
|
|
170
|
+
if (target.lower && target.lower.value === lower.value) target.lower.inclusive &&= lower.inclusive;
|
|
171
|
+
else if (!target.lower || !(target.lower.value > lower.value)) target.lower = { ...lower };
|
|
172
|
+
}
|
|
173
|
+
const upper = source.upper;
|
|
174
|
+
if (upper) {
|
|
175
|
+
if (target.upper && target.upper.value === upper.value) target.upper.inclusive &&= upper.inclusive;
|
|
176
|
+
else if (!target.upper || !(target.upper.value < upper.value)) target.upper = { ...upper };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function addConditionFeature(features, name, operator, value) {
|
|
180
|
+
const colon = name.indexOf(":");
|
|
181
|
+
const feature = colon < 0 ? name : name.slice(0, colon);
|
|
182
|
+
const unit = colon < 0 ? "" : name.slice(colon + 1);
|
|
183
|
+
const bound = {
|
|
184
|
+
value,
|
|
185
|
+
inclusive: operator.includes("=")
|
|
186
|
+
};
|
|
187
|
+
const source = {
|
|
188
|
+
domain: "",
|
|
189
|
+
feature,
|
|
190
|
+
unit,
|
|
191
|
+
lower: null,
|
|
192
|
+
upper: null
|
|
193
|
+
};
|
|
194
|
+
if (operator === ">" || operator === ">=") source.lower = bound;
|
|
195
|
+
else if (operator === "<" || operator === "<=") source.upper = bound;
|
|
196
|
+
else if (operator === "=") {
|
|
197
|
+
source.lower = bound;
|
|
198
|
+
source.upper = { ...bound };
|
|
199
|
+
} else return;
|
|
200
|
+
const target = features.find((item) => item.feature === feature && item.unit === unit);
|
|
201
|
+
if (target) intersectRange(target, source);
|
|
202
|
+
else features.push(source);
|
|
203
|
+
}
|
|
204
|
+
const REVERSED = {
|
|
205
|
+
">": "<",
|
|
206
|
+
">=": "<=",
|
|
207
|
+
"<": ">",
|
|
208
|
+
"<=": ">=",
|
|
209
|
+
"=": "="
|
|
210
|
+
};
|
|
211
|
+
function addRangeFeatures(features, body) {
|
|
212
|
+
const colon = body.indexOf(":");
|
|
213
|
+
if (colon >= 0) {
|
|
214
|
+
let name = trim(body.slice(0, colon));
|
|
215
|
+
let operator = "=";
|
|
216
|
+
if (name.startsWith("min-")) [name, operator] = [name.slice(4), ">="];
|
|
217
|
+
else if (name.startsWith("max-")) [name, operator] = [name.slice(4), "<="];
|
|
218
|
+
const dimension = rangeDimension(trim(body.slice(colon + 1)));
|
|
219
|
+
if (!dimension || !RANGE_FEATURES.has(name)) return false;
|
|
220
|
+
addConditionFeature(features, `${name}:${dimension[1]}`, operator, dimension[0]);
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
const operands = [];
|
|
224
|
+
const operators = [];
|
|
225
|
+
let remaining = body;
|
|
226
|
+
for (let index = remaining.search(/[<>=]/); index >= 0; index = remaining.search(/[<>=]/)) {
|
|
227
|
+
operands.push(trim(remaining.slice(0, index)));
|
|
228
|
+
remaining = remaining.slice(index);
|
|
229
|
+
const end = remaining.search(/[^<>=]/);
|
|
230
|
+
operators.push(end < 0 ? remaining : remaining.slice(0, end));
|
|
231
|
+
remaining = end < 0 ? "" : remaining.slice(end);
|
|
232
|
+
}
|
|
233
|
+
operands.push(trim(remaining));
|
|
234
|
+
if (!operators.length || operators.length > 2) return false;
|
|
235
|
+
if (operators.length === 2 && (!RANGE_FEATURES.has(operands[1]) || operators[0][0] === "=" || operators[0][0] !== operators[1][0])) return false;
|
|
236
|
+
for (const [index, operator] of operators.entries()) {
|
|
237
|
+
const [left, right] = [operands[index], operands[index + 1]];
|
|
238
|
+
let name;
|
|
239
|
+
let value;
|
|
240
|
+
let direction = operator;
|
|
241
|
+
if (RANGE_FEATURES.has(left)) [name, value] = [left, right];
|
|
242
|
+
else if (RANGE_FEATURES.has(right)) [name, value, direction] = [
|
|
243
|
+
right,
|
|
244
|
+
left,
|
|
245
|
+
Object.hasOwn(REVERSED, operator) ? REVERSED[operator] : void 0
|
|
246
|
+
];
|
|
247
|
+
else return false;
|
|
248
|
+
if (direction === void 0 || !Object.hasOwn(REVERSED, direction)) return false;
|
|
249
|
+
const dimension = rangeDimension(value);
|
|
250
|
+
if (!dimension) return false;
|
|
251
|
+
addConditionFeature(features, `${name}:${dimension[1]}`, direction, dimension[0]);
|
|
252
|
+
}
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
const sortFeatures = (features) => features.sort((left, right) => naturalCompare(left.domain, right.domain) || naturalCompare(left.feature, right.feature) || naturalCompare(left.unit, right.unit));
|
|
256
|
+
/** Numeric ranges of a conjunction of simple range features with identical units; empty otherwise. */
|
|
257
|
+
function nativeQueryFeatures(query) {
|
|
258
|
+
let domain;
|
|
259
|
+
let body;
|
|
260
|
+
if (query.startsWith("@media ")) {
|
|
261
|
+
domain = "media";
|
|
262
|
+
body = trim(query.slice(7));
|
|
263
|
+
} else if (query.startsWith("@container ")) {
|
|
264
|
+
body = trim(query.slice(11));
|
|
265
|
+
if (body[0] === "(") domain = "container:";
|
|
266
|
+
else {
|
|
267
|
+
let space = 0;
|
|
268
|
+
while (space < body.length && !isWhitespace(body.charCodeAt(space))) space++;
|
|
269
|
+
if (space === body.length) return [];
|
|
270
|
+
const name = body.slice(0, space);
|
|
271
|
+
for (let index = 0; index < name.length; index++) if (!isNameCodePoint(name.charCodeAt(index))) return [];
|
|
272
|
+
domain = `container:${name}`;
|
|
273
|
+
body = trim(body.slice(space + 1));
|
|
274
|
+
}
|
|
275
|
+
} else return [];
|
|
276
|
+
const features = [];
|
|
277
|
+
for (const part of body.split(" and ")) {
|
|
278
|
+
const trimmed = trim(part);
|
|
279
|
+
if (trimmed[0] !== "(" || !trimmed.endsWith(")")) return [];
|
|
280
|
+
const inner = trimmed.slice(1, -1);
|
|
281
|
+
if (/[()'"]/.test(inner) || !addRangeFeatures(features, trim(inner))) return [];
|
|
282
|
+
}
|
|
283
|
+
for (const feature of features) feature.domain = domain;
|
|
284
|
+
return sortFeatures(features);
|
|
285
|
+
}
|
|
286
|
+
function mergeConditionFeatures(target, source) {
|
|
287
|
+
for (const feature of source) {
|
|
288
|
+
const existing = target.find((item) => item.domain === "media" && item.domain === feature.domain && item.feature === feature.feature && item.unit === feature.unit);
|
|
289
|
+
if (existing) intersectRange(existing, feature);
|
|
290
|
+
else target.push(feature);
|
|
291
|
+
}
|
|
292
|
+
sortFeatures(target);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Ordering evidence of condition wrappers. Only a wholly numeric media conjunction
|
|
296
|
+
* intersects across wrappers; container ancestry and other conditions keep their order.
|
|
297
|
+
*/
|
|
298
|
+
function conditionPriority(wrappers) {
|
|
299
|
+
const parsed = wrappers.map(([, wrapper]) => nativeQueryFeatures(wrapper));
|
|
300
|
+
const mediaConjunction = wrappers.every(([kind], index) => kind === "media" && parsed[index].length);
|
|
301
|
+
const features = [];
|
|
302
|
+
const conditions = [];
|
|
303
|
+
for (const [index, [, wrapper]] of wrappers.entries()) {
|
|
304
|
+
const ranges = parsed[index];
|
|
305
|
+
if (!ranges.length) conditions.push(`${index}:${trim(wrapper)}`);
|
|
306
|
+
else if (mediaConjunction) mergeConditionFeatures(features, ranges);
|
|
307
|
+
else for (const range of ranges) {
|
|
308
|
+
range.domain = `${index}:${range.domain}`;
|
|
309
|
+
features.push(range);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return [features, conditions];
|
|
313
|
+
}
|
|
314
|
+
/** Unwinds the media query parser; never leaves it. */
|
|
315
|
+
const MEDIA_ERROR = /* @__PURE__ */ new Error("Invalid media query");
|
|
316
|
+
const MAX_MEDIA_DEPTH = 128;
|
|
317
|
+
/** Parses a media query that references custom media, resolving `(--name)` through `resolve`. */
|
|
318
|
+
function parseCustomMediaQuery(source, resolve) {
|
|
319
|
+
const tokens = tokenize(source);
|
|
320
|
+
if (tokens.length === 1 && tokens[0].kind === 0) {
|
|
321
|
+
if (isAsciiCase(tokens[0].value, "true")) return { type: "true" };
|
|
322
|
+
if (isAsciiCase(tokens[0].value, "false")) return { type: "false" };
|
|
323
|
+
}
|
|
324
|
+
let depth = 0;
|
|
325
|
+
for (const token of tokens) {
|
|
326
|
+
if (token.close >= 0 && ++depth > MAX_MEDIA_DEPTH) return;
|
|
327
|
+
if (token.kind === 8 && (token.value === ")" || token.value === "]" || token.value === "}") && depth) depth--;
|
|
328
|
+
}
|
|
329
|
+
const fail = () => {
|
|
330
|
+
throw MEDIA_ERROR;
|
|
331
|
+
};
|
|
332
|
+
const ident = (index, name) => tokens[index]?.kind === 0 && isAsciiCase(tokens[index].value, name);
|
|
333
|
+
const top = (start, end) => {
|
|
334
|
+
const result = [];
|
|
335
|
+
for (let index = start; index < end;) {
|
|
336
|
+
result.push(index);
|
|
337
|
+
const token = tokens[index];
|
|
338
|
+
if (token.kind === 3 || isDelim(token, "(") || isDelim(token, "[") || isDelim(token, "{")) {
|
|
339
|
+
if (token.close < 0 || token.close >= end) fail();
|
|
340
|
+
index = token.close + 1;
|
|
341
|
+
} else index++;
|
|
342
|
+
}
|
|
343
|
+
return result;
|
|
344
|
+
};
|
|
345
|
+
const list = (start, end) => {
|
|
346
|
+
const queries = [];
|
|
347
|
+
let cursor = start;
|
|
348
|
+
for (const index of top(start, end)) if (isDelim(tokens[index], ",")) {
|
|
349
|
+
queries.push(query(cursor, index));
|
|
350
|
+
cursor = index + 1;
|
|
351
|
+
}
|
|
352
|
+
queries.push(query(cursor, end));
|
|
353
|
+
return queries.length === 1 ? queries[0] : {
|
|
354
|
+
type: "or",
|
|
355
|
+
queries
|
|
356
|
+
};
|
|
357
|
+
};
|
|
358
|
+
const query = (start, end) => {
|
|
359
|
+
if (start >= end) fail();
|
|
360
|
+
const items = top(start, end);
|
|
361
|
+
const qualifier = ident(start, "not") || ident(start, "only");
|
|
362
|
+
const typeIndex = start + (qualifier ? 1 : 0);
|
|
363
|
+
const typeToken = tokens[typeIndex];
|
|
364
|
+
if (typeToken?.kind === 0) {
|
|
365
|
+
const name = typeToken.value;
|
|
366
|
+
if (isKeyword(name) || name.startsWith("--")) fail();
|
|
367
|
+
let result = isAsciiCase(name, "all") ? { type: "true" } : {
|
|
368
|
+
type: "media-type",
|
|
369
|
+
name: asciiLowercase(name)
|
|
370
|
+
};
|
|
371
|
+
if (typeIndex + 1 < end) {
|
|
372
|
+
if (!ident(typeIndex + 1, "and")) fail();
|
|
373
|
+
const inner = condition(typeIndex + 2, end);
|
|
374
|
+
if (top(typeIndex + 2, end).some((index) => ident(index, "or"))) fail();
|
|
375
|
+
result = {
|
|
376
|
+
type: "and",
|
|
377
|
+
queries: [result, inner]
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
return ident(start, "not") ? {
|
|
381
|
+
type: "not",
|
|
382
|
+
query: result
|
|
383
|
+
} : result;
|
|
384
|
+
}
|
|
385
|
+
if (qualifier && ident(start, "only")) fail();
|
|
386
|
+
if (!items.length) fail();
|
|
387
|
+
return condition(start, end);
|
|
388
|
+
};
|
|
389
|
+
const condition = (start, end) => {
|
|
390
|
+
const items = top(start, end);
|
|
391
|
+
if (!items.length) fail();
|
|
392
|
+
if (ident(start, "not")) {
|
|
393
|
+
if (items.length !== 2) fail();
|
|
394
|
+
return {
|
|
395
|
+
type: "not",
|
|
396
|
+
query: atom(items[1], end)
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
const queries = [];
|
|
400
|
+
let operator;
|
|
401
|
+
for (const [position, index] of items.entries()) {
|
|
402
|
+
if (position % 2 === 0) {
|
|
403
|
+
queries.push(atom(index, items[position + 1] ?? end));
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
const or = ident(index, "or") ? true : ident(index, "and") ? false : fail();
|
|
407
|
+
if (operator !== void 0 && operator !== or) fail();
|
|
408
|
+
operator = or;
|
|
409
|
+
}
|
|
410
|
+
if (items.length % 2 === 0) fail();
|
|
411
|
+
return operator === void 0 ? queries[0] : {
|
|
412
|
+
type: operator ? "or" : "and",
|
|
413
|
+
queries
|
|
414
|
+
};
|
|
415
|
+
};
|
|
416
|
+
const atom = (start, end) => {
|
|
417
|
+
const token = tokens[start];
|
|
418
|
+
if (token.close < 0 || token.close + 1 !== end) fail();
|
|
419
|
+
const close = token.close;
|
|
420
|
+
if (isDelim(token, "(")) {
|
|
421
|
+
const inner = top(start + 1, close);
|
|
422
|
+
if (inner.length === 1 && tokens[inner[0]].kind === 0 && tokens[inner[0]].value.startsWith("--")) return resolve(tokens[inner[0]].value) ?? fail();
|
|
423
|
+
const first = tokens[inner[0]];
|
|
424
|
+
if (first && (isDelim(first, "(") || first.kind === 3 || ident(inner[0], "not"))) return condition(start + 1, close);
|
|
425
|
+
if (inner.some((index) => tokens[index].kind === 0 && tokens[index].value.startsWith("--"))) fail();
|
|
426
|
+
if (!inner.length) fail();
|
|
427
|
+
} else if (token.kind !== 3) fail();
|
|
428
|
+
return {
|
|
429
|
+
type: "feature",
|
|
430
|
+
value: source.slice(token.start, tokens[close].end)
|
|
431
|
+
};
|
|
432
|
+
};
|
|
433
|
+
try {
|
|
434
|
+
return list(0, tokens.length);
|
|
435
|
+
} catch (error) {
|
|
436
|
+
if (error === MEDIA_ERROR) return;
|
|
437
|
+
throw error;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
export { conditionPriority, legacyQuery, nativeQueryFeatures, parseCustomMediaQuery, parseNativeQuery, queryRequiresCss, renderConditionToken, resolveLayerCondition };
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { isAsciiDigit } from "./text.js";
|
|
2
|
+
const isNewline = (unit) => unit === 10 || unit === 13 || unit === 12;
|
|
3
|
+
const isHex = (unit) => isAsciiDigit(unit) || unit >= 97 && unit <= 102 || unit >= 65 && unit <= 70;
|
|
4
|
+
const isLetter = (unit) => unit >= 97 && unit <= 122 || unit >= 65 && unit <= 90;
|
|
5
|
+
const isNameStart = (unit) => isLetter(unit) || unit === 95 || unit >= 128;
|
|
6
|
+
const isCssWhitespace = (unit) => unit === 32 || unit === 9 || unit === 10 || unit === 13 || unit === 12;
|
|
7
|
+
/** A CSS name code point: an ASCII letter or digit, `-`, `_` or any non-ASCII code point. */
|
|
8
|
+
const isNameCodePoint = (unit) => isNameStart(unit) || isAsciiDigit(unit) || unit === 45;
|
|
9
|
+
function startsIdentifier(source, index) {
|
|
10
|
+
if (index >= source.length) return false;
|
|
11
|
+
const validEscape = (at) => at < source.length && !isNewline(source.charCodeAt(at));
|
|
12
|
+
const first = source.charCodeAt(index);
|
|
13
|
+
if (first === 45) {
|
|
14
|
+
if (index + 1 >= source.length) return false;
|
|
15
|
+
const next = source.charCodeAt(index + 1);
|
|
16
|
+
return next === 92 ? validEscape(index + 2) : isNameStart(next) || next === 45;
|
|
17
|
+
}
|
|
18
|
+
return first === 92 ? validEscape(index + 1) : isNameStart(first);
|
|
19
|
+
}
|
|
20
|
+
/** The end of a signed CSS number with an optional fraction and exponent at `index`, or -1. */
|
|
21
|
+
function numberEnd(source, index) {
|
|
22
|
+
let cursor = index;
|
|
23
|
+
const sign = source.charCodeAt(cursor);
|
|
24
|
+
if (sign === 43 || sign === 45) cursor++;
|
|
25
|
+
const start = cursor;
|
|
26
|
+
while (isAsciiDigit(source.charCodeAt(cursor))) cursor++;
|
|
27
|
+
if (source.charCodeAt(cursor) === 46 && isAsciiDigit(source.charCodeAt(cursor + 1))) {
|
|
28
|
+
cursor += 2;
|
|
29
|
+
while (isAsciiDigit(source.charCodeAt(cursor))) cursor++;
|
|
30
|
+
} else if (cursor === start) return -1;
|
|
31
|
+
const exponent = source.charCodeAt(cursor);
|
|
32
|
+
if (exponent === 101 || exponent === 69) {
|
|
33
|
+
let digits = cursor + 1;
|
|
34
|
+
const exponentSign = source.charCodeAt(digits);
|
|
35
|
+
if (exponentSign === 43 || exponentSign === 45) digits++;
|
|
36
|
+
if (isAsciiDigit(source.charCodeAt(digits))) {
|
|
37
|
+
cursor = digits + 1;
|
|
38
|
+
while (isAsciiDigit(source.charCodeAt(cursor))) cursor++;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return cursor;
|
|
42
|
+
}
|
|
43
|
+
let text = "";
|
|
44
|
+
let at = 0;
|
|
45
|
+
/** Consumes the escape whose backslash is at `at`. An escaped newline and the end of input decode to nothing. */
|
|
46
|
+
function escape() {
|
|
47
|
+
at++;
|
|
48
|
+
if (at >= text.length) return "";
|
|
49
|
+
const first = text.charCodeAt(at);
|
|
50
|
+
if (isNewline(first)) {
|
|
51
|
+
at++;
|
|
52
|
+
if (first === 13 && text.charCodeAt(at) === 10) at++;
|
|
53
|
+
return "";
|
|
54
|
+
}
|
|
55
|
+
if (isHex(first)) {
|
|
56
|
+
const start = at;
|
|
57
|
+
while (at - start < 6 && isHex(text.charCodeAt(at))) at++;
|
|
58
|
+
const value = parseInt(text.slice(start, at), 16);
|
|
59
|
+
const whitespace = text.charCodeAt(at);
|
|
60
|
+
if (isCssWhitespace(whitespace)) {
|
|
61
|
+
at++;
|
|
62
|
+
if (whitespace === 13 && text.charCodeAt(at) === 10) at++;
|
|
63
|
+
}
|
|
64
|
+
return value === 0 || value > 1114111 || value >= 55296 && value <= 57343 ? "�" : String.fromCodePoint(value);
|
|
65
|
+
}
|
|
66
|
+
const character = String.fromCodePoint(text.codePointAt(at));
|
|
67
|
+
at += character.length;
|
|
68
|
+
return character;
|
|
69
|
+
}
|
|
70
|
+
function name() {
|
|
71
|
+
const start = at;
|
|
72
|
+
let decoded;
|
|
73
|
+
while (at < text.length) {
|
|
74
|
+
const unit = text.charCodeAt(at);
|
|
75
|
+
if (unit === 92 && at + 1 < text.length && !isNewline(text.charCodeAt(at + 1))) {
|
|
76
|
+
decoded ??= text.slice(start, at);
|
|
77
|
+
decoded += escape();
|
|
78
|
+
} else if (isNameCodePoint(unit)) {
|
|
79
|
+
at++;
|
|
80
|
+
if (decoded !== void 0) decoded += text[at - 1];
|
|
81
|
+
} else break;
|
|
82
|
+
}
|
|
83
|
+
return decoded ?? text.slice(start, at);
|
|
84
|
+
}
|
|
85
|
+
/** The end and decoded name of the identifier starting at `index`. */
|
|
86
|
+
function readName(source, index) {
|
|
87
|
+
text = source;
|
|
88
|
+
at = index;
|
|
89
|
+
const value = name();
|
|
90
|
+
return [at, value];
|
|
91
|
+
}
|
|
92
|
+
function skipComment(source, index) {
|
|
93
|
+
const end = source.indexOf("*/", index + 2);
|
|
94
|
+
return end < 0 ? source.length : end + 2;
|
|
95
|
+
}
|
|
96
|
+
/** The name of the leading function token, without tokenizing the rest. */
|
|
97
|
+
function leadingFunctionName(source) {
|
|
98
|
+
let index = 0;
|
|
99
|
+
while (index < source.length) {
|
|
100
|
+
const unit = source.charCodeAt(index);
|
|
101
|
+
if (isCssWhitespace(unit)) index++;
|
|
102
|
+
else if (unit === 47 && source.charCodeAt(index + 1) === 42) index = skipComment(source, index);
|
|
103
|
+
else break;
|
|
104
|
+
}
|
|
105
|
+
const unit = source.charCodeAt(index);
|
|
106
|
+
if (unit === 34 || unit === 39 || numberEnd(source, index) >= 0 || unit === 64 && startsIdentifier(source, index + 1)) return;
|
|
107
|
+
if (!startsIdentifier(source, index)) return;
|
|
108
|
+
text = source;
|
|
109
|
+
at = index;
|
|
110
|
+
const value = name();
|
|
111
|
+
return at > index && source.charCodeAt(at) === 40 ? value : void 0;
|
|
112
|
+
}
|
|
113
|
+
function tokenize(source) {
|
|
114
|
+
const tokens = [];
|
|
115
|
+
const opens = [];
|
|
116
|
+
text = source;
|
|
117
|
+
at = 0;
|
|
118
|
+
while (at < source.length) {
|
|
119
|
+
const start = at;
|
|
120
|
+
const unit = source.charCodeAt(at);
|
|
121
|
+
if (isCssWhitespace(unit)) {
|
|
122
|
+
at++;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (unit === 47 && source.charCodeAt(at + 1) === 42) {
|
|
126
|
+
at = skipComment(source, at);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
let kind;
|
|
130
|
+
let value = "";
|
|
131
|
+
let dimension = "";
|
|
132
|
+
let end;
|
|
133
|
+
if (unit === 34 || unit === 39) {
|
|
134
|
+
at++;
|
|
135
|
+
const contentStart = at;
|
|
136
|
+
let decoded;
|
|
137
|
+
let valid = true;
|
|
138
|
+
while (at < source.length) {
|
|
139
|
+
const next = source.charCodeAt(at);
|
|
140
|
+
if (next === unit) break;
|
|
141
|
+
if (isNewline(next)) {
|
|
142
|
+
valid = false;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
if (next === 92) {
|
|
146
|
+
decoded ??= source.slice(contentStart, at);
|
|
147
|
+
decoded += escape();
|
|
148
|
+
} else {
|
|
149
|
+
at++;
|
|
150
|
+
if (decoded !== void 0) decoded += source[at - 1];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
value = decoded ?? source.slice(contentStart, at);
|
|
154
|
+
if (source.charCodeAt(at) === unit) at++;
|
|
155
|
+
kind = valid ? 1 : 7;
|
|
156
|
+
} else if ((end = numberEnd(source, at)) >= 0) {
|
|
157
|
+
at = end;
|
|
158
|
+
value = source.slice(start, at);
|
|
159
|
+
if (startsIdentifier(source, at)) {
|
|
160
|
+
kind = 5;
|
|
161
|
+
dimension = name();
|
|
162
|
+
} else if (source.charCodeAt(at) === 37) {
|
|
163
|
+
at++;
|
|
164
|
+
kind = 6;
|
|
165
|
+
} else kind = 4;
|
|
166
|
+
} else if (unit === 64 && startsIdentifier(source, at + 1)) {
|
|
167
|
+
at++;
|
|
168
|
+
value = name();
|
|
169
|
+
kind = value ? 2 : 8;
|
|
170
|
+
if (!value) value = "@";
|
|
171
|
+
} else if (startsIdentifier(source, at)) {
|
|
172
|
+
value = name();
|
|
173
|
+
if (at === start) {
|
|
174
|
+
at++;
|
|
175
|
+
kind = 7;
|
|
176
|
+
} else if (source.charCodeAt(at) === 40) {
|
|
177
|
+
at++;
|
|
178
|
+
kind = 3;
|
|
179
|
+
} else kind = 0;
|
|
180
|
+
} else {
|
|
181
|
+
at++;
|
|
182
|
+
kind = 8;
|
|
183
|
+
value = source[start];
|
|
184
|
+
}
|
|
185
|
+
const index = tokens.length;
|
|
186
|
+
if (kind === 3 || kind === 8 && (value === "(" || value === "[" || value === "{")) opens.push(index);
|
|
187
|
+
else if (kind === 8 && opens.length && closer(tokens[opens[opens.length - 1]]) === value) tokens[opens.pop()].close = index;
|
|
188
|
+
tokens.push({
|
|
189
|
+
kind,
|
|
190
|
+
value,
|
|
191
|
+
unit: dimension,
|
|
192
|
+
start,
|
|
193
|
+
end: at,
|
|
194
|
+
close: -1
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return tokens;
|
|
198
|
+
}
|
|
199
|
+
const closer = (token) => token.kind === 3 || token.value === "(" ? ")" : token.value === "[" ? "]" : "}";
|
|
200
|
+
const isDelim = (token, value) => token?.kind === 8 && token.value === value;
|
|
201
|
+
/** Statement and block structure with parent indexes, as `collect_css_syntax_statements` walks it. */
|
|
202
|
+
function collectStatements(tokens) {
|
|
203
|
+
const result = [];
|
|
204
|
+
const blocks = [[
|
|
205
|
+
0,
|
|
206
|
+
tokens.length,
|
|
207
|
+
-1
|
|
208
|
+
]];
|
|
209
|
+
while (blocks.length) {
|
|
210
|
+
let [start, end, parent] = blocks.pop();
|
|
211
|
+
while (start < end) {
|
|
212
|
+
if (isDelim(tokens[start], ";") || isDelim(tokens[start], "}")) {
|
|
213
|
+
start++;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
const declaration = parent >= 0 && tokens[start].kind === 0 && isDelim(tokens[start + 1], ":");
|
|
217
|
+
const custom = declaration && tokens[start].value.startsWith("--");
|
|
218
|
+
let cursor = start;
|
|
219
|
+
while (cursor < end) {
|
|
220
|
+
const token = tokens[cursor];
|
|
221
|
+
if (isDelim(token, ";") || !custom && isDelim(token, "{")) break;
|
|
222
|
+
if (token.kind === 3 || isDelim(token, "(") || isDelim(token, "[") || isDelim(token, "{")) cursor = token.close < 0 ? end : token.close + 1;
|
|
223
|
+
else cursor++;
|
|
224
|
+
}
|
|
225
|
+
const hasBlock = cursor < end && isDelim(tokens[cursor], "{");
|
|
226
|
+
const index = result.length;
|
|
227
|
+
result.push({
|
|
228
|
+
start,
|
|
229
|
+
end: cursor,
|
|
230
|
+
parent,
|
|
231
|
+
hasBlock,
|
|
232
|
+
declaration: declaration && !hasBlock
|
|
233
|
+
});
|
|
234
|
+
if (hasBlock) {
|
|
235
|
+
const close = Math.min(tokens[cursor].close < 0 ? end : tokens[cursor].close, end);
|
|
236
|
+
blocks.push([
|
|
237
|
+
cursor + 1,
|
|
238
|
+
close,
|
|
239
|
+
index
|
|
240
|
+
]);
|
|
241
|
+
start = close + 1;
|
|
242
|
+
} else start = cursor + 1;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
export { collectStatements, isCssWhitespace, isNameCodePoint, leadingFunctionName, numberEnd, readName, startsIdentifier, tokenize };
|