css-typed-om-polyfill 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +119 -185
- package/README.zh.md +115 -182
- package/cssom.js +1732 -0
- package/package.json +3 -3
- package/css-typed-om-polyfill.js +0 -1369
package/cssom.js
ADDED
|
@@ -0,0 +1,1732 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Moonlight CSS Typed OM Polyfill
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Zero-Allocation Lexer with Raw Token Capture
|
|
6
|
+
* - Algebraic Simplification Engine (Strict Type Safety)
|
|
7
|
+
* - Recursive Fallback Parsing for CSS Variables
|
|
8
|
+
* - Enhanced Error Handling and Type Safety
|
|
9
|
+
*
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
(function (global) {
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
// --- 0. Environment Check ---
|
|
16
|
+
if (typeof global === 'undefined') return;
|
|
17
|
+
if (global.CSS && global.CSS.number && global.CSSNumericValue) {
|
|
18
|
+
console.log("%c Moonlight %c Native Support Detected. Sleeping. ",
|
|
19
|
+
"background:#bd93f9;color:white", "background:#333;color:white");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// --- 1. High-Performance Constants ---
|
|
24
|
+
const UNIT_MAP = {
|
|
25
|
+
'%': 'percent', 'percent': 'percent',
|
|
26
|
+
'px': 'length', 'cm': 'length', 'mm': 'length', 'in': 'length',
|
|
27
|
+
'pt': 'length', 'pc': 'length',
|
|
28
|
+
'em': 'length', 'rem': 'length', 'vw': 'length', 'vh': 'length',
|
|
29
|
+
'vmin': 'length', 'vmax': 'length', 'ch': 'length', 'ex': 'length',
|
|
30
|
+
'q': 'length', 'vi': 'length', 'vb': 'length',
|
|
31
|
+
'deg': 'angle', 'rad': 'angle', 'grad': 'angle', 'turn': 'angle',
|
|
32
|
+
's': 'time', 'ms': 'time',
|
|
33
|
+
'Hz': 'frequency', 'kHz': 'frequency',
|
|
34
|
+
'dpi': 'resolution', 'dpcm': 'resolution', 'dppx': 'resolution',
|
|
35
|
+
'fr': 'flex',
|
|
36
|
+
'number': 'number', '': 'number'
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const BASE_TYPES = {
|
|
40
|
+
length: 0, angle: 0, time: 0, frequency: 0,
|
|
41
|
+
resolution: 0, flex: 0, percent: 0
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const STRICT_PROPS = {
|
|
45
|
+
'width': 1, 'height': 1, 'min-width': 1, 'min-height': 1,
|
|
46
|
+
'max-width': 1, 'max-height': 1,
|
|
47
|
+
'top': 1, 'left': 1, 'right': 1, 'bottom': 1,
|
|
48
|
+
'margin': 1, 'padding': 1, 'font-size': 1,
|
|
49
|
+
'transform': 1, 'rotate': 1, 'scale': 1, 'translate': 1,
|
|
50
|
+
'opacity': 1, 'z-index': 1,
|
|
51
|
+
'flex-grow': 1, 'flex-shrink': 1, 'order': 1
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// LRU Cache for kebab-case conversion
|
|
55
|
+
class LRUCache {
|
|
56
|
+
constructor(maxSize = 1000) {
|
|
57
|
+
this.maxSize = maxSize;
|
|
58
|
+
this.cache = new Map();
|
|
59
|
+
}
|
|
60
|
+
get(key) {
|
|
61
|
+
if (!this.cache.has(key)) return undefined;
|
|
62
|
+
const value = this.cache.get(key);
|
|
63
|
+
this.cache.delete(key);
|
|
64
|
+
this.cache.set(key, value);
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
set(key, value) {
|
|
68
|
+
if (this.cache.has(key)) {
|
|
69
|
+
this.cache.delete(key);
|
|
70
|
+
} else if (this.cache.size >= this.maxSize) {
|
|
71
|
+
const firstKey = this.cache.keys().next().value;
|
|
72
|
+
this.cache.delete(firstKey);
|
|
73
|
+
}
|
|
74
|
+
this.cache.set(key, value);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const KEBAB_CACHE = new LRUCache(500);
|
|
79
|
+
|
|
80
|
+
// --- 2. Algebraic Engine ---
|
|
81
|
+
|
|
82
|
+
const createType = (unit) => {
|
|
83
|
+
const t = { ...BASE_TYPES };
|
|
84
|
+
const cat = UNIT_MAP[unit];
|
|
85
|
+
if (!cat) {
|
|
86
|
+
throw new TypeError(`Unknown unit: ${unit}`);
|
|
87
|
+
}
|
|
88
|
+
if (cat !== 'number' && t.hasOwnProperty(cat)) {
|
|
89
|
+
t[cat] = 1;
|
|
90
|
+
}
|
|
91
|
+
return t;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Type compatibility checker
|
|
95
|
+
const areTypesCompatible = (type1, type2) => {
|
|
96
|
+
// Special case: percent and length are compatible in CSS
|
|
97
|
+
// e.g., calc(100% - 20px) is valid
|
|
98
|
+
const hasPercent1 = type1.percent > 0;
|
|
99
|
+
const hasPercent2 = type2.percent > 0;
|
|
100
|
+
const hasLength1 = type1.length > 0;
|
|
101
|
+
const hasLength2 = type2.length > 0;
|
|
102
|
+
|
|
103
|
+
// If one has percent and the other has length (but not both), they're compatible
|
|
104
|
+
if ((hasPercent1 || hasLength1) && (hasPercent2 || hasLength2)) {
|
|
105
|
+
// Check all other dimensions are zero
|
|
106
|
+
for (let key in BASE_TYPES) {
|
|
107
|
+
if (key === 'percent' || key === 'length') continue;
|
|
108
|
+
if (type1[key] !== 0 || type2[key] !== 0) return false;
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Otherwise, types must match exactly
|
|
114
|
+
for (let key in BASE_TYPES) {
|
|
115
|
+
if (type1[key] !== type2[key]) return false;
|
|
116
|
+
}
|
|
117
|
+
return true;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
function simplifySum(args, _skipRecursion = false) {
|
|
121
|
+
const hasVar = args.some(a => a instanceof CSSVariableReferenceValue);
|
|
122
|
+
|
|
123
|
+
// Fast Path: Pure Units
|
|
124
|
+
if (!hasVar && args.length === 2 &&
|
|
125
|
+
args[0] instanceof CSSUnitValue &&
|
|
126
|
+
args[1] instanceof CSSUnitValue &&
|
|
127
|
+
args[0].unit === args[1].unit) {
|
|
128
|
+
return new CSSUnitValue(args[0].value + args[1].value, args[0].unit);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let flat = [];
|
|
132
|
+
let hasSum = false;
|
|
133
|
+
for (let arg of args) {
|
|
134
|
+
if (arg instanceof CSSMathSum) {
|
|
135
|
+
hasSum = true;
|
|
136
|
+
flat.push(...arg.values);
|
|
137
|
+
} else {
|
|
138
|
+
flat.push(arg);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const target = hasSum ? flat : args;
|
|
142
|
+
|
|
143
|
+
// If variables exist, return without folding
|
|
144
|
+
if (hasVar) {
|
|
145
|
+
return target.length === 1 ? target[0] : target;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Type compatibility check
|
|
149
|
+
let hasNum = false;
|
|
150
|
+
let hasDimension = false;
|
|
151
|
+
let firstDimType = null;
|
|
152
|
+
|
|
153
|
+
for (let arg of target) {
|
|
154
|
+
if (arg instanceof CSSUnitValue) {
|
|
155
|
+
if (arg.unit === 'number') {
|
|
156
|
+
hasNum = true;
|
|
157
|
+
} else {
|
|
158
|
+
hasDimension = true;
|
|
159
|
+
const argType = arg.type();
|
|
160
|
+
|
|
161
|
+
if (!firstDimType) {
|
|
162
|
+
firstDimType = argType;
|
|
163
|
+
} else if (!areTypesCompatible(firstDimType, argType)) {
|
|
164
|
+
throw new TypeError(
|
|
165
|
+
`Incompatible types in sum: Cannot add ${JSON.stringify(firstDimType)} and ${JSON.stringify(argType)}`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Pure numbers cannot mix with dimensions
|
|
173
|
+
if (hasNum && hasDimension) {
|
|
174
|
+
throw new TypeError("Incompatible types: Cannot add Number and Dimension.");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Fold compatible units (only exact same unit)
|
|
178
|
+
const bucket = {};
|
|
179
|
+
const complex = [];
|
|
180
|
+
|
|
181
|
+
for (let arg of target) {
|
|
182
|
+
if (arg instanceof CSSUnitValue) {
|
|
183
|
+
// Only fold identical units, not compatible ones (e.g., don't fold % and px)
|
|
184
|
+
bucket[arg.unit] = (bucket[arg.unit] || 0) + arg.value;
|
|
185
|
+
} else {
|
|
186
|
+
complex.push(arg);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const folded = [];
|
|
191
|
+
for (let u in bucket) {
|
|
192
|
+
folded.push(new CSSUnitValue(bucket[u], u));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const result = [...folded, ...complex];
|
|
196
|
+
if (result.length === 0) return new CSSUnitValue(0, 'number');
|
|
197
|
+
if (result.length === 1 && complex.length === 0) return result[0];
|
|
198
|
+
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function simplifyProduct(args) {
|
|
203
|
+
if (args.some(a => a instanceof CSSVariableReferenceValue)) return null;
|
|
204
|
+
|
|
205
|
+
let scalar = 1;
|
|
206
|
+
let unitVal = null;
|
|
207
|
+
let sumNode = null;
|
|
208
|
+
let other = [];
|
|
209
|
+
|
|
210
|
+
for (let arg of args) {
|
|
211
|
+
let val = null;
|
|
212
|
+
|
|
213
|
+
// Resolve scalars (including Invert/Negate)
|
|
214
|
+
if (arg instanceof CSSUnitValue && arg.unit === 'number') {
|
|
215
|
+
val = arg.value;
|
|
216
|
+
} else if (arg instanceof CSSMathNegate &&
|
|
217
|
+
arg.value instanceof CSSUnitValue &&
|
|
218
|
+
arg.value.unit === 'number') {
|
|
219
|
+
val = -arg.value.value;
|
|
220
|
+
} else if (arg instanceof CSSMathInvert &&
|
|
221
|
+
arg.value instanceof CSSUnitValue &&
|
|
222
|
+
arg.value.unit === 'number') {
|
|
223
|
+
if (arg.value.value === 0) {
|
|
224
|
+
throw new RangeError("Division by zero");
|
|
225
|
+
}
|
|
226
|
+
val = 1 / arg.value.value;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (val !== null) {
|
|
230
|
+
scalar *= val;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (arg instanceof CSSUnitValue) {
|
|
235
|
+
if (unitVal) {
|
|
236
|
+
other.push(arg); // Multiple dimensions
|
|
237
|
+
} else {
|
|
238
|
+
unitVal = arg;
|
|
239
|
+
}
|
|
240
|
+
} else if (arg instanceof CSSMathSum) {
|
|
241
|
+
if (sumNode) {
|
|
242
|
+
other.push(arg);
|
|
243
|
+
} else {
|
|
244
|
+
sumNode = arg;
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
other.push(arg);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (scalar === 0) return new CSSUnitValue(0, 'number');
|
|
252
|
+
if (!unitVal && !sumNode && other.length === 0) {
|
|
253
|
+
return new CSSUnitValue(scalar, 'number');
|
|
254
|
+
}
|
|
255
|
+
if (unitVal && !sumNode && other.length === 0) {
|
|
256
|
+
return new CSSUnitValue(unitVal.value * scalar, unitVal.unit);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Distribution: (A + B) * s
|
|
260
|
+
if (sumNode && !unitVal && other.length === 0 && scalar !== 1) {
|
|
261
|
+
const distributed = sumNode.values.map(t =>
|
|
262
|
+
t.mul(new CSSUnitValue(scalar, 'number'))
|
|
263
|
+
);
|
|
264
|
+
return new CSSMathSum(...distributed);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// --- 3. The Lexer (Moonlight Scanner) ---
|
|
271
|
+
const TT = {
|
|
272
|
+
EOF: 0, ERR: 1, NUM: 2, DIM: 3, OP: 4,
|
|
273
|
+
OPEN: 5, CLOSE: 6, COMMA: 7, IDENT: 8, FUNC: 9
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
class Scanner {
|
|
277
|
+
constructor(text) {
|
|
278
|
+
this.text = text;
|
|
279
|
+
this.len = text.length;
|
|
280
|
+
this.pos = 0;
|
|
281
|
+
this.type = TT.EOF;
|
|
282
|
+
this.str = '';
|
|
283
|
+
this.num = 0;
|
|
284
|
+
this.unit = '';
|
|
285
|
+
this.raw = '';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
scan() {
|
|
289
|
+
// Skip whitespace
|
|
290
|
+
while (this.pos < this.len && this.text.charCodeAt(this.pos) <= 32) {
|
|
291
|
+
this.pos++;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (this.pos >= this.len) {
|
|
295
|
+
this.type = TT.EOF;
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const start = this.pos;
|
|
300
|
+
const c = this.text.charCodeAt(this.pos);
|
|
301
|
+
|
|
302
|
+
// Operators & Punctuation
|
|
303
|
+
if (c === 40) { // (
|
|
304
|
+
this.type = TT.OPEN;
|
|
305
|
+
this.pos++;
|
|
306
|
+
this.raw = '(';
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (c === 41) { // )
|
|
310
|
+
this.type = TT.CLOSE;
|
|
311
|
+
this.pos++;
|
|
312
|
+
this.raw = ')';
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (c === 44) { // ,
|
|
316
|
+
this.type = TT.COMMA;
|
|
317
|
+
this.pos++;
|
|
318
|
+
this.raw = ',';
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (c === 42 || c === 47 || c === 43) { // * / +
|
|
322
|
+
this.type = TT.OP;
|
|
323
|
+
this.str = this.text[this.pos++];
|
|
324
|
+
this.raw = this.str;
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Minus (could be OP or start of IDENT/NUM)
|
|
329
|
+
if (c === 45) { // -
|
|
330
|
+
const next = this.pos + 1;
|
|
331
|
+
if (next < this.len) {
|
|
332
|
+
const c2 = this.text.charCodeAt(next);
|
|
333
|
+
if ((c2 >= 48 && c2 <= 57) || c2 === 46) { // digit or .
|
|
334
|
+
this._number();
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (c2 === 45 || this._isIdentStart(c2)) { // --custom or -webkit
|
|
338
|
+
this._ident();
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
this.type = TT.OP;
|
|
343
|
+
this.str = '-';
|
|
344
|
+
this.pos++;
|
|
345
|
+
this.raw = '-';
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if ((c >= 48 && c <= 57) || c === 46) { // digit or .
|
|
350
|
+
this._number();
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (this._isIdentStart(c)) {
|
|
354
|
+
this._ident();
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
this.type = TT.ERR;
|
|
359
|
+
this.str = this.text[this.pos++];
|
|
360
|
+
this.raw = this.str;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
_number() {
|
|
364
|
+
const start = this.pos;
|
|
365
|
+
|
|
366
|
+
// Sign
|
|
367
|
+
if (this.text[this.pos] === '+' || this.text[this.pos] === '-') {
|
|
368
|
+
this.pos++;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Integer and decimal part
|
|
372
|
+
let hasDigit = false;
|
|
373
|
+
while (this.pos < this.len) {
|
|
374
|
+
const c = this.text.charCodeAt(this.pos);
|
|
375
|
+
if (c >= 48 && c <= 57) { // 0-9
|
|
376
|
+
this.pos++;
|
|
377
|
+
hasDigit = true;
|
|
378
|
+
} else if (c === 46) { // .
|
|
379
|
+
this.pos++;
|
|
380
|
+
} else {
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Scientific notation
|
|
386
|
+
if (this.pos < this.len) {
|
|
387
|
+
const c = this.text.charCodeAt(this.pos);
|
|
388
|
+
if (c === 69 || c === 101) { // E or e
|
|
389
|
+
let p = this.pos + 1;
|
|
390
|
+
if (p < this.len &&
|
|
391
|
+
(this.text.charCodeAt(p) === 43 ||
|
|
392
|
+
this.text.charCodeAt(p) === 45)) { // + or -
|
|
393
|
+
p++;
|
|
394
|
+
}
|
|
395
|
+
const sciStart = p;
|
|
396
|
+
while (p < this.len &&
|
|
397
|
+
this.text.charCodeAt(p) >= 48 &&
|
|
398
|
+
this.text.charCodeAt(p) <= 57) {
|
|
399
|
+
p++;
|
|
400
|
+
}
|
|
401
|
+
if (p > sciStart) { // Valid scientific notation
|
|
402
|
+
this.pos = p;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const numStr = this.text.slice(start, this.pos);
|
|
408
|
+
this.num = parseFloat(numStr);
|
|
409
|
+
|
|
410
|
+
if (!isFinite(this.num)) {
|
|
411
|
+
throw new TypeError(`Invalid number: ${numStr}`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Unit
|
|
415
|
+
const uStart = this.pos;
|
|
416
|
+
if (this.pos < this.len && this.text.charCodeAt(this.pos) === 37) { // %
|
|
417
|
+
this.pos++;
|
|
418
|
+
this.type = TT.DIM;
|
|
419
|
+
this.unit = 'percent';
|
|
420
|
+
} else {
|
|
421
|
+
while (this.pos < this.len &&
|
|
422
|
+
this._isIdentChar(this.text.charCodeAt(this.pos))) {
|
|
423
|
+
this.pos++;
|
|
424
|
+
}
|
|
425
|
+
const rawUnit = this.text.slice(uStart, this.pos).toLowerCase();
|
|
426
|
+
|
|
427
|
+
if (!rawUnit) {
|
|
428
|
+
this.type = TT.NUM;
|
|
429
|
+
this.unit = 'number';
|
|
430
|
+
} else {
|
|
431
|
+
if (!UNIT_MAP.hasOwnProperty(rawUnit)) {
|
|
432
|
+
throw new TypeError(`Invalid unit: ${rawUnit}`);
|
|
433
|
+
}
|
|
434
|
+
this.type = TT.DIM;
|
|
435
|
+
this.unit = rawUnit;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
this.raw = this.text.slice(start, this.pos);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
_ident() {
|
|
443
|
+
const start = this.pos;
|
|
444
|
+
|
|
445
|
+
while (this.pos < this.len &&
|
|
446
|
+
this._isIdentChar(this.text.charCodeAt(this.pos))) {
|
|
447
|
+
this.pos++;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
this.str = this.text.slice(start, this.pos);
|
|
451
|
+
|
|
452
|
+
// Lookahead for Function
|
|
453
|
+
let p = this.pos;
|
|
454
|
+
while (p < this.len && this.text.charCodeAt(p) <= 32) p++;
|
|
455
|
+
|
|
456
|
+
if (p < this.len && this.text.charCodeAt(p) === 40) { // (
|
|
457
|
+
this.pos = p + 1; // Consume '('
|
|
458
|
+
this.type = TT.FUNC;
|
|
459
|
+
this.str = this.str.toLowerCase();
|
|
460
|
+
this.raw = this.text.slice(start, this.pos);
|
|
461
|
+
} else {
|
|
462
|
+
this.type = TT.IDENT;
|
|
463
|
+
this.raw = this.str;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
_isIdentStart(c) {
|
|
468
|
+
return (c >= 65 && c <= 90) || // A-Z
|
|
469
|
+
(c >= 97 && c <= 122) || // a-z
|
|
470
|
+
c === 95 || // _
|
|
471
|
+
c === 45 || // -
|
|
472
|
+
c > 127; // Non-ASCII
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
_isIdentChar(c) {
|
|
476
|
+
return this._isIdentStart(c) ||
|
|
477
|
+
(c >= 48 && c <= 57); // 0-9
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// --- 4. CSS Typed OM Core Classes ---
|
|
482
|
+
|
|
483
|
+
class CSSStyleValue {
|
|
484
|
+
constructor() {
|
|
485
|
+
if (this.constructor === CSSStyleValue) {
|
|
486
|
+
throw new TypeError("CSSStyleValue is an abstract class");
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
toString() { return ''; }
|
|
491
|
+
|
|
492
|
+
static parse(prop, val) {
|
|
493
|
+
return Parser.parse(prop, val);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
static parseAll(prop, val) {
|
|
497
|
+
return [Parser.parse(prop, val)];
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
class CSSNumericValue extends CSSStyleValue {
|
|
502
|
+
add(...args) {
|
|
503
|
+
return new CSSMathSum(this, ...args);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
sub(...args) {
|
|
507
|
+
return new CSSMathSum(
|
|
508
|
+
this,
|
|
509
|
+
...args.map(a => CSSNumericValue.from(a).negate())
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
mul(...args) {
|
|
514
|
+
return new CSSMathProduct(this, ...args);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
div(...args) {
|
|
518
|
+
return new CSSMathProduct(
|
|
519
|
+
this,
|
|
520
|
+
...args.map(a => CSSNumericValue.from(a).invert())
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
min(...args) {
|
|
525
|
+
return new CSSMathMin(this, ...args);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
max(...args) {
|
|
529
|
+
return new CSSMathMax(this, ...args);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
negate() {
|
|
533
|
+
return new CSSMathNegate(this);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
invert() {
|
|
537
|
+
return new CSSMathInvert(this);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
to(unit) {
|
|
541
|
+
if (this instanceof CSSUnitValue && this.unit === unit) {
|
|
542
|
+
return this;
|
|
543
|
+
}
|
|
544
|
+
// Simplified conversion - full implementation would handle unit conversion
|
|
545
|
+
return new CSSUnitValue(this.value, unit);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
type() {
|
|
549
|
+
throw new Error("type() method not implemented in subclass");
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
static from(v) {
|
|
553
|
+
if (v instanceof CSSNumericValue) return v;
|
|
554
|
+
if (typeof v === 'number') return new CSSUnitValue(v, 'number');
|
|
555
|
+
if (v instanceof CSSVariableReferenceValue) {
|
|
556
|
+
// Allow variables in numeric contexts
|
|
557
|
+
return v;
|
|
558
|
+
}
|
|
559
|
+
throw new TypeError(
|
|
560
|
+
`Cannot convert to CSSNumericValue: ${v}`
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
class CSSUnitValue extends CSSNumericValue {
|
|
566
|
+
constructor(val, unit) {
|
|
567
|
+
super();
|
|
568
|
+
if (typeof val !== 'number') {
|
|
569
|
+
throw new TypeError("Value must be a number");
|
|
570
|
+
}
|
|
571
|
+
if (!isFinite(val)) {
|
|
572
|
+
throw new TypeError("Value must be finite");
|
|
573
|
+
}
|
|
574
|
+
if (typeof unit !== 'string') {
|
|
575
|
+
throw new TypeError("Unit must be a string");
|
|
576
|
+
}
|
|
577
|
+
if (!UNIT_MAP.hasOwnProperty(unit)) {
|
|
578
|
+
throw new TypeError(`Invalid unit: ${unit}`);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
this.value = val;
|
|
582
|
+
this.unit = unit;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
toString() {
|
|
586
|
+
// Better precision handling
|
|
587
|
+
let s = this.value;
|
|
588
|
+
|
|
589
|
+
// Round to 6 decimal places, but remove trailing zeros
|
|
590
|
+
if (Math.abs(s) < 1e10 && Math.abs(s) > 1e-6) {
|
|
591
|
+
s = Math.round(s * 1e6) / 1e6;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const str = String(s);
|
|
595
|
+
|
|
596
|
+
if (this.unit === 'number') {
|
|
597
|
+
return str;
|
|
598
|
+
} else if (this.unit === 'percent') {
|
|
599
|
+
return str + '%';
|
|
600
|
+
} else {
|
|
601
|
+
return str + this.unit;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
type() {
|
|
606
|
+
return createType(this.unit);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
class CSSVariableReferenceValue extends CSSStyleValue {
|
|
611
|
+
constructor(variable, fallback = null) {
|
|
612
|
+
super();
|
|
613
|
+
|
|
614
|
+
if (typeof variable !== 'string') {
|
|
615
|
+
throw new TypeError("Variable must be a string");
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
this.variable = variable;
|
|
619
|
+
this.fallback = fallback;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
toString() {
|
|
623
|
+
return `var(${this.variable}${this.fallback ? ', ' + this.fallback.toString() : ''
|
|
624
|
+
})`;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
type() {
|
|
628
|
+
return {};
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Math Interop
|
|
632
|
+
add(...args) { return new CSSMathSum(this, ...args); }
|
|
633
|
+
mul(...args) { return new CSSMathProduct(this, ...args); }
|
|
634
|
+
sub(...args) {
|
|
635
|
+
return new CSSMathSum(
|
|
636
|
+
this,
|
|
637
|
+
...args.map(a => CSSNumericValue.from(a).negate())
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
div(...args) {
|
|
641
|
+
return new CSSMathProduct(
|
|
642
|
+
this,
|
|
643
|
+
...args.map(a => CSSNumericValue.from(a).invert())
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
negate() { return new CSSMathNegate(this); }
|
|
647
|
+
invert() { return new CSSMathInvert(this); }
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
class CSSUnparsedValue extends CSSStyleValue {
|
|
651
|
+
constructor(members) {
|
|
652
|
+
super();
|
|
653
|
+
|
|
654
|
+
if (!Array.isArray(members)) {
|
|
655
|
+
throw new TypeError("Members must be an array");
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
this.members = members;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
toString() {
|
|
662
|
+
return this.members.join('');
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
[Symbol.iterator]() {
|
|
666
|
+
return this.members[Symbol.iterator]();
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
get length() {
|
|
670
|
+
return this.members.length;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
class CSSKeywordValue extends CSSStyleValue {
|
|
675
|
+
constructor(value) {
|
|
676
|
+
super();
|
|
677
|
+
|
|
678
|
+
if (typeof value !== 'string') {
|
|
679
|
+
throw new TypeError("Keyword value must be a string");
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
this.value = value;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
toString() {
|
|
686
|
+
return this.value;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// --- 5. Math Objects ---
|
|
691
|
+
|
|
692
|
+
const fmt = (v) => {
|
|
693
|
+
let s = v.toString();
|
|
694
|
+
return s.startsWith('calc(') ? s.slice(5, -1) : s;
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
const wrap = (v) => {
|
|
698
|
+
return (v instanceof CSSMathSum || v instanceof CSSMathNegate)
|
|
699
|
+
? `(${fmt(v)})`
|
|
700
|
+
: fmt(v);
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
class CSSMathValue extends CSSNumericValue { }
|
|
704
|
+
|
|
705
|
+
class CSSMathSum extends CSSMathValue {
|
|
706
|
+
constructor(...args) {
|
|
707
|
+
super();
|
|
708
|
+
|
|
709
|
+
if (args.length === 0) {
|
|
710
|
+
throw new TypeError("CSSMathSum requires at least one argument");
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const input = args.map(CSSNumericValue.from);
|
|
714
|
+
const sim = simplifySum(input, true);
|
|
715
|
+
|
|
716
|
+
if (sim instanceof CSSUnitValue) {
|
|
717
|
+
// Return the simplified unit value directly
|
|
718
|
+
return sim;
|
|
719
|
+
} else if (Array.isArray(sim)) {
|
|
720
|
+
// Store the array directly to avoid recursion
|
|
721
|
+
this.values = sim;
|
|
722
|
+
} else {
|
|
723
|
+
// Single value
|
|
724
|
+
this.values = [sim];
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
toString() {
|
|
729
|
+
if (!this.values || this.values.length === 0) {
|
|
730
|
+
return 'calc(0)';
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const parts = this.values.map((v, i) => {
|
|
734
|
+
if (i === 0) {
|
|
735
|
+
return fmt(v);
|
|
736
|
+
} else if (v instanceof CSSMathNegate) {
|
|
737
|
+
return ' - ' + fmt(v.value);
|
|
738
|
+
} else {
|
|
739
|
+
return ' + ' + fmt(v);
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
return `calc(${parts.join('')})`;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
type() {
|
|
747
|
+
if (!this.values || this.values.length === 0) {
|
|
748
|
+
return { ...BASE_TYPES };
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Merge types: if any value has percent OR length, result has both
|
|
752
|
+
let hasPercent = false;
|
|
753
|
+
let hasLength = false;
|
|
754
|
+
const result = { ...BASE_TYPES };
|
|
755
|
+
|
|
756
|
+
for (let v of this.values) {
|
|
757
|
+
if (v && v.type) {
|
|
758
|
+
const t = v.type();
|
|
759
|
+
if (t.percent > 0) hasPercent = true;
|
|
760
|
+
if (t.length > 0) hasLength = true;
|
|
761
|
+
|
|
762
|
+
// Merge other dimensions
|
|
763
|
+
for (let key in BASE_TYPES) {
|
|
764
|
+
if (key === 'percent' || key === 'length') continue;
|
|
765
|
+
if (t[key] > 0) result[key] = Math.max(result[key], t[key]);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// If we have both percent and length, set both flags
|
|
771
|
+
// This represents calc(% ± length) which is valid
|
|
772
|
+
if (hasPercent || hasLength) {
|
|
773
|
+
result.percent = hasPercent ? 1 : 0;
|
|
774
|
+
result.length = hasLength ? 1 : 0;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
return result;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
class CSSMathProduct extends CSSMathValue {
|
|
782
|
+
constructor(...args) {
|
|
783
|
+
super();
|
|
784
|
+
|
|
785
|
+
if (args.length === 0) {
|
|
786
|
+
throw new TypeError("CSSMathProduct requires at least one argument");
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
this.values = args.map(CSSNumericValue.from);
|
|
790
|
+
const sim = simplifyProduct(this.values);
|
|
791
|
+
|
|
792
|
+
if (sim) {
|
|
793
|
+
return sim;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
toString() {
|
|
798
|
+
let n = [], d = [];
|
|
799
|
+
|
|
800
|
+
this.values.forEach(v => {
|
|
801
|
+
if (v instanceof CSSMathInvert) {
|
|
802
|
+
d.push(v.value);
|
|
803
|
+
} else {
|
|
804
|
+
n.push(v);
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
if (!n.length) {
|
|
809
|
+
n.push(new CSSUnitValue(1, 'number'));
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const ns = n.map(wrap).join(' * ');
|
|
813
|
+
const ds = d.map(wrap).join(' * ');
|
|
814
|
+
|
|
815
|
+
if (!ds) {
|
|
816
|
+
return `calc(${ns})`;
|
|
817
|
+
} else {
|
|
818
|
+
return `calc(${ns} / ${d.length > 1 ? `(${ds})` : ds})`;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
type() {
|
|
823
|
+
return this.values.reduce((acc, v) => {
|
|
824
|
+
if (v && v.type) {
|
|
825
|
+
return Object.assign(acc, v.type());
|
|
826
|
+
}
|
|
827
|
+
return acc;
|
|
828
|
+
}, { ...BASE_TYPES });
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
class CSSMathNegate extends CSSMathValue {
|
|
833
|
+
constructor(v) {
|
|
834
|
+
super();
|
|
835
|
+
|
|
836
|
+
this.value = CSSNumericValue.from(v);
|
|
837
|
+
|
|
838
|
+
// Simplification
|
|
839
|
+
if (this.value instanceof CSSUnitValue) {
|
|
840
|
+
return new CSSUnitValue(-this.value.value, this.value.unit);
|
|
841
|
+
}
|
|
842
|
+
if (this.value instanceof CSSMathNegate) {
|
|
843
|
+
return this.value.value;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
toString() {
|
|
848
|
+
return `calc(-1 * ${wrap(this.value)})`;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
type() {
|
|
852
|
+
return this.value.type ? this.value.type() : {};
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
class CSSMathInvert extends CSSMathValue {
|
|
857
|
+
constructor(v) {
|
|
858
|
+
super();
|
|
859
|
+
|
|
860
|
+
this.value = CSSNumericValue.from(v);
|
|
861
|
+
|
|
862
|
+
// Simplification
|
|
863
|
+
if (this.value instanceof CSSUnitValue &&
|
|
864
|
+
this.value.unit === 'number') {
|
|
865
|
+
if (this.value.value === 0) {
|
|
866
|
+
throw new RangeError("Cannot invert zero");
|
|
867
|
+
}
|
|
868
|
+
return new CSSUnitValue(1 / this.value.value, 'number');
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
toString() {
|
|
873
|
+
return `calc(1 / ${wrap(this.value)})`;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
type() {
|
|
877
|
+
return {};
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
class CSSMathMin extends CSSMathValue {
|
|
882
|
+
constructor(...args) {
|
|
883
|
+
super();
|
|
884
|
+
|
|
885
|
+
if (args.length === 0) {
|
|
886
|
+
throw new TypeError("CSSMathMin requires at least one argument");
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
this.values = args.map(CSSNumericValue.from);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
toString() {
|
|
893
|
+
return `min(${this.values.map(v => v.toString()).join(', ')})`;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
type() {
|
|
897
|
+
return this.values[0] && this.values[0].type
|
|
898
|
+
? this.values[0].type()
|
|
899
|
+
: {};
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
class CSSMathMax extends CSSMathValue {
|
|
904
|
+
constructor(...args) {
|
|
905
|
+
super();
|
|
906
|
+
|
|
907
|
+
if (args.length === 0) {
|
|
908
|
+
throw new TypeError("CSSMathMax requires at least one argument");
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
this.values = args.map(CSSNumericValue.from);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
toString() {
|
|
915
|
+
return `max(${this.values.map(v => v.toString()).join(', ')})`;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
type() {
|
|
919
|
+
return this.values[0] && this.values[0].type
|
|
920
|
+
? this.values[0].type()
|
|
921
|
+
: {};
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
class CSSMathClamp extends CSSMathValue {
|
|
926
|
+
constructor(min, val, max) {
|
|
927
|
+
super();
|
|
928
|
+
|
|
929
|
+
this.lower = CSSNumericValue.from(min);
|
|
930
|
+
this.value = CSSNumericValue.from(val);
|
|
931
|
+
this.upper = CSSNumericValue.from(max);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
toString() {
|
|
935
|
+
return `clamp(${this.lower}, ${this.value}, ${this.upper})`;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
type() {
|
|
939
|
+
return this.value.type ? this.value.type() : {};
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// --- 6. Transforms ---
|
|
944
|
+
|
|
945
|
+
class CSSTransformComponent extends CSSStyleValue {
|
|
946
|
+
constructor() {
|
|
947
|
+
super();
|
|
948
|
+
this.is2D = true;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
toMatrix() {
|
|
952
|
+
if (typeof DOMMatrix !== 'undefined') {
|
|
953
|
+
return new DOMMatrix(this.toString());
|
|
954
|
+
}
|
|
955
|
+
throw new Error("DOMMatrix not available");
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
class CSSTranslate extends CSSTransformComponent {
|
|
960
|
+
constructor(x, y, z) {
|
|
961
|
+
super();
|
|
962
|
+
this.x = CSSNumericValue.from(x);
|
|
963
|
+
this.y = CSSNumericValue.from(y);
|
|
964
|
+
this.z = z ? CSSNumericValue.from(z) : new CSSUnitValue(0, 'px');
|
|
965
|
+
this.is2D = !z || (z instanceof CSSUnitValue && z.value === 0);
|
|
966
|
+
}
|
|
967
|
+
toString() {
|
|
968
|
+
return this.is2D
|
|
969
|
+
? `translate(${this.x}, ${this.y})`
|
|
970
|
+
: `translate3d(${this.x}, ${this.y}, ${this.z})`;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
class CSSRotate extends CSSTransformComponent {
|
|
975
|
+
constructor(...args) {
|
|
976
|
+
super();
|
|
977
|
+
|
|
978
|
+
if (args.length === 1) {
|
|
979
|
+
this.x = new CSSUnitValue(0, 'number');
|
|
980
|
+
this.y = new CSSUnitValue(0, 'number');
|
|
981
|
+
this.z = new CSSUnitValue(1, 'number');
|
|
982
|
+
this.angle = CSSNumericValue.from(args[0]);
|
|
983
|
+
this.is2D = true;
|
|
984
|
+
} else if (args.length === 4) {
|
|
985
|
+
this.x = CSSNumericValue.from(args[0]);
|
|
986
|
+
this.y = CSSNumericValue.from(args[1]);
|
|
987
|
+
this.z = CSSNumericValue.from(args[2]);
|
|
988
|
+
this.angle = CSSNumericValue.from(args[3]);
|
|
989
|
+
this.is2D = false;
|
|
990
|
+
} else {
|
|
991
|
+
throw new TypeError(
|
|
992
|
+
"CSSRotate requires 1 or 4 arguments"
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
toString() {
|
|
998
|
+
return this.is2D
|
|
999
|
+
? `rotate(${this.angle})`
|
|
1000
|
+
: `rotate3d(${this.x}, ${this.y}, ${this.z}, ${this.angle})`;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
class CSSScale extends CSSTransformComponent {
|
|
1005
|
+
constructor(x, y, z) {
|
|
1006
|
+
super();
|
|
1007
|
+
this.x = CSSNumericValue.from(x);
|
|
1008
|
+
this.y = CSSNumericValue.from(y !== undefined ? y : x);
|
|
1009
|
+
this.z = z ? CSSNumericValue.from(z) : new CSSUnitValue(1, 'number');
|
|
1010
|
+
this.is2D = !z || (z instanceof CSSUnitValue && z.value === 1);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
toString() {
|
|
1014
|
+
return this.is2D
|
|
1015
|
+
? `scale(${this.x}, ${this.y})`
|
|
1016
|
+
: `scale3d(${this.x}, ${this.y}, ${this.z})`;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
class CSSSkew extends CSSTransformComponent {
|
|
1021
|
+
constructor(x, y) {
|
|
1022
|
+
super();
|
|
1023
|
+
this.ax = CSSNumericValue.from(x);
|
|
1024
|
+
this.ay = CSSNumericValue.from(y);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
toString() {
|
|
1028
|
+
return `skew(${this.ax}, ${this.ay})`;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
class CSSSkewX extends CSSTransformComponent {
|
|
1033
|
+
constructor(x) {
|
|
1034
|
+
super();
|
|
1035
|
+
this.ax = CSSNumericValue.from(x);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
toString() {
|
|
1039
|
+
return `skewX(${this.ax})`;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
class CSSSkewY extends CSSTransformComponent {
|
|
1044
|
+
constructor(y) {
|
|
1045
|
+
super();
|
|
1046
|
+
this.ay = CSSNumericValue.from(y);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
toString() {
|
|
1050
|
+
return `skewY(${this.ay})`;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
class CSSPerspective extends CSSTransformComponent {
|
|
1055
|
+
constructor(length) {
|
|
1056
|
+
super();
|
|
1057
|
+
this.length = CSSNumericValue.from(length);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
toString() {
|
|
1061
|
+
return `perspective(${this.length})`;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
class CSSMatrixComponent extends CSSTransformComponent {
|
|
1066
|
+
constructor(a, b, c, d, e, f) {
|
|
1067
|
+
super();
|
|
1068
|
+
this.a = CSSNumericValue.from(a);
|
|
1069
|
+
this.b = CSSNumericValue.from(b);
|
|
1070
|
+
this.c = CSSNumericValue.from(c);
|
|
1071
|
+
this.d = CSSNumericValue.from(d);
|
|
1072
|
+
this.e = CSSNumericValue.from(e);
|
|
1073
|
+
this.f = CSSNumericValue.from(f);
|
|
1074
|
+
this.is2D = true;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
toString() {
|
|
1078
|
+
return `matrix(${this.a}, ${this.b}, ${this.c}, ${this.d}, ${this.e}, ${this.f})`;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
class CSSTransformValue extends CSSStyleValue {
|
|
1083
|
+
constructor(transforms) {
|
|
1084
|
+
super();
|
|
1085
|
+
|
|
1086
|
+
if (!Array.isArray(transforms)) {
|
|
1087
|
+
throw new TypeError("Transforms must be an array");
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
this.length = transforms.length;
|
|
1091
|
+
this.is2D = transforms.every(c => c.is2D);
|
|
1092
|
+
|
|
1093
|
+
for (let i = 0; i < transforms.length; i++) {
|
|
1094
|
+
this[i] = transforms[i];
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
toString() {
|
|
1099
|
+
return Array.from(this).map(t => t.toString()).join(' ');
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
[Symbol.iterator]() {
|
|
1103
|
+
let i = 0;
|
|
1104
|
+
const self = this;
|
|
1105
|
+
return {
|
|
1106
|
+
next: () => ({
|
|
1107
|
+
value: self[i],
|
|
1108
|
+
done: i++ >= self.length
|
|
1109
|
+
})
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
toMatrix() {
|
|
1114
|
+
if (typeof DOMMatrix === 'undefined') {
|
|
1115
|
+
throw new Error("DOMMatrix not available");
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
let m = new DOMMatrix();
|
|
1119
|
+
for (let i = 0; i < this.length; i++) {
|
|
1120
|
+
m = m.multiply(this[i].toMatrix());
|
|
1121
|
+
}
|
|
1122
|
+
return m;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// --- 7. Parser ---
|
|
1127
|
+
|
|
1128
|
+
const Parser = {
|
|
1129
|
+
parse(prop, text) {
|
|
1130
|
+
text = String(text).trim();
|
|
1131
|
+
|
|
1132
|
+
if (!text) {
|
|
1133
|
+
throw new TypeError(`Empty value for property "${prop}"`);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// Special handling for transform
|
|
1137
|
+
if (prop === 'transform') {
|
|
1138
|
+
try {
|
|
1139
|
+
return this.parseTransform(text);
|
|
1140
|
+
} catch (e) {
|
|
1141
|
+
if (STRICT_PROPS[prop] && !text.includes('var(')) {
|
|
1142
|
+
throw new TypeError(`Invalid transform: ${text}`);
|
|
1143
|
+
}
|
|
1144
|
+
return new CSSUnparsedValue([text]);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
try {
|
|
1149
|
+
const s = new Scanner(text);
|
|
1150
|
+
s.scan();
|
|
1151
|
+
const res = this.expr(s);
|
|
1152
|
+
|
|
1153
|
+
if (s.type !== TT.EOF) {
|
|
1154
|
+
throw new Error("Unexpected tokens after expression");
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
return res;
|
|
1158
|
+
} catch (e) {
|
|
1159
|
+
// Strict properties must parse correctly unless they contain variables
|
|
1160
|
+
if (STRICT_PROPS[prop] && !text.includes('var(')) {
|
|
1161
|
+
throw new TypeError(
|
|
1162
|
+
`Invalid value for ${prop}: ${text}. Error: ${e.message}`
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// Fallback to unparsed value
|
|
1167
|
+
return new CSSUnparsedValue([text]);
|
|
1168
|
+
}
|
|
1169
|
+
},
|
|
1170
|
+
|
|
1171
|
+
expr(s) {
|
|
1172
|
+
let left = this.term(s);
|
|
1173
|
+
|
|
1174
|
+
while (s.type === TT.OP && (s.str === '+' || s.str === '-')) {
|
|
1175
|
+
const op = s.str;
|
|
1176
|
+
s.scan();
|
|
1177
|
+
const right = this.term(s);
|
|
1178
|
+
|
|
1179
|
+
left = (op === '+')
|
|
1180
|
+
? new CSSMathSum(left, right)
|
|
1181
|
+
: new CSSMathSum(left, new CSSMathNegate(right));
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
return left;
|
|
1185
|
+
},
|
|
1186
|
+
|
|
1187
|
+
term(s) {
|
|
1188
|
+
let left = this.unary(s);
|
|
1189
|
+
|
|
1190
|
+
while (s.type === TT.OP && (s.str === '*' || s.str === '/')) {
|
|
1191
|
+
const op = s.str;
|
|
1192
|
+
s.scan();
|
|
1193
|
+
const right = this.unary(s);
|
|
1194
|
+
|
|
1195
|
+
left = (op === '*')
|
|
1196
|
+
? new CSSMathProduct(left, right)
|
|
1197
|
+
: new CSSMathProduct(left, new CSSMathInvert(right));
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
return left;
|
|
1201
|
+
},
|
|
1202
|
+
|
|
1203
|
+
unary(s) {
|
|
1204
|
+
// Handle unary minus
|
|
1205
|
+
if (s.type === TT.OP && s.str === '-') {
|
|
1206
|
+
s.scan();
|
|
1207
|
+
return new CSSMathNegate(this.unary(s));
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// Handle unary plus (just ignore it)
|
|
1211
|
+
if (s.type === TT.OP && s.str === '+') {
|
|
1212
|
+
s.scan();
|
|
1213
|
+
return this.unary(s);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
return this.factor(s);
|
|
1217
|
+
},
|
|
1218
|
+
|
|
1219
|
+
factor(s) {
|
|
1220
|
+
// Numbers and dimensions
|
|
1221
|
+
if (s.type === TT.NUM || s.type === TT.DIM) {
|
|
1222
|
+
const n = new CSSUnitValue(s.num, s.unit);
|
|
1223
|
+
s.scan();
|
|
1224
|
+
return n;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// Parentheses
|
|
1228
|
+
if (s.type === TT.OPEN) {
|
|
1229
|
+
s.scan();
|
|
1230
|
+
const n = this.expr(s);
|
|
1231
|
+
|
|
1232
|
+
if (s.type !== TT.CLOSE) {
|
|
1233
|
+
throw new Error("Expected closing parenthesis");
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
s.scan();
|
|
1237
|
+
return n;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// Functions
|
|
1241
|
+
if (s.type === TT.FUNC) {
|
|
1242
|
+
const name = s.str;
|
|
1243
|
+
const funcStart = s.pos;
|
|
1244
|
+
s.scan(); // consume 'func('
|
|
1245
|
+
|
|
1246
|
+
// Special handling for var()
|
|
1247
|
+
if (name === 'var') {
|
|
1248
|
+
if (s.type !== TT.IDENT) {
|
|
1249
|
+
throw new Error("Expected variable name after var(");
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
const varName = s.str;
|
|
1253
|
+
s.scan();
|
|
1254
|
+
|
|
1255
|
+
let fallback = null;
|
|
1256
|
+
|
|
1257
|
+
if (s.type === TT.COMMA) {
|
|
1258
|
+
s.scan();
|
|
1259
|
+
|
|
1260
|
+
// Capture fallback using text slicing to preserve format
|
|
1261
|
+
const fbStart = s.pos;
|
|
1262
|
+
let balance = 0;
|
|
1263
|
+
|
|
1264
|
+
while (s.type !== TT.EOF) {
|
|
1265
|
+
if (s.type === TT.CLOSE && balance === 0) {
|
|
1266
|
+
break;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
if (s.type === TT.OPEN) {
|
|
1270
|
+
balance++;
|
|
1271
|
+
} else if (s.type === TT.FUNC) {
|
|
1272
|
+
balance++;
|
|
1273
|
+
} else if (s.type === TT.CLOSE) {
|
|
1274
|
+
balance--;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
s.scan();
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
const fbEnd = s.pos;
|
|
1281
|
+
const fbText = s.text.slice(fbStart, fbEnd).trim();
|
|
1282
|
+
|
|
1283
|
+
if (fbText) {
|
|
1284
|
+
fallback = new CSSUnparsedValue([fbText]);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
if (s.type !== TT.CLOSE) {
|
|
1289
|
+
throw new Error("Expected closing parenthesis for var()");
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
s.scan();
|
|
1293
|
+
return new CSSVariableReferenceValue(varName, fallback);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
// Other functions
|
|
1297
|
+
const args = [];
|
|
1298
|
+
|
|
1299
|
+
if (s.type !== TT.CLOSE) {
|
|
1300
|
+
while (true) {
|
|
1301
|
+
args.push(this.expr(s));
|
|
1302
|
+
|
|
1303
|
+
if (s.type === TT.COMMA) {
|
|
1304
|
+
s.scan();
|
|
1305
|
+
} else {
|
|
1306
|
+
break;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
if (s.type !== TT.CLOSE) {
|
|
1312
|
+
throw new Error(`Expected closing parenthesis for ${name}()`);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
s.scan(); // consume ')'
|
|
1316
|
+
|
|
1317
|
+
// Handle different function types
|
|
1318
|
+
if (name === 'calc') {
|
|
1319
|
+
if (args.length !== 1) {
|
|
1320
|
+
throw new Error("calc() requires exactly one argument");
|
|
1321
|
+
}
|
|
1322
|
+
return args[0];
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
if (name === 'min') {
|
|
1326
|
+
if (args.length === 0) {
|
|
1327
|
+
throw new Error("min() requires at least one argument");
|
|
1328
|
+
}
|
|
1329
|
+
return new CSSMathMin(...args);
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
if (name === 'max') {
|
|
1333
|
+
if (args.length === 0) {
|
|
1334
|
+
throw new Error("max() requires at least one argument");
|
|
1335
|
+
}
|
|
1336
|
+
return new CSSMathMax(...args);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
if (name === 'clamp') {
|
|
1340
|
+
if (args.length !== 3) {
|
|
1341
|
+
throw new Error("clamp() requires exactly 3 arguments");
|
|
1342
|
+
}
|
|
1343
|
+
return new CSSMathClamp(args[0], args[1], args[2]);
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
throw new Error(`Unknown function: ${name}()`);
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// Keywords
|
|
1350
|
+
if (s.type === TT.IDENT) {
|
|
1351
|
+
const v = new CSSKeywordValue(s.str);
|
|
1352
|
+
s.scan();
|
|
1353
|
+
return v;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
throw new Error(
|
|
1357
|
+
`Unexpected token: ${s.type} (${s.str || s.raw || 'EOF'})`
|
|
1358
|
+
);
|
|
1359
|
+
},
|
|
1360
|
+
|
|
1361
|
+
parseTransform(text) {
|
|
1362
|
+
const s = new Scanner(text);
|
|
1363
|
+
s.scan();
|
|
1364
|
+
const list = [];
|
|
1365
|
+
|
|
1366
|
+
while (s.type !== TT.EOF) {
|
|
1367
|
+
if (s.type !== TT.FUNC) {
|
|
1368
|
+
throw new Error(
|
|
1369
|
+
`Expected transform function, got: ${s.type}`
|
|
1370
|
+
);
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
const name = s.str;
|
|
1374
|
+
s.scan();
|
|
1375
|
+
|
|
1376
|
+
const args = [];
|
|
1377
|
+
|
|
1378
|
+
if (s.type !== TT.CLOSE) {
|
|
1379
|
+
while (true) {
|
|
1380
|
+
args.push(this.expr(s));
|
|
1381
|
+
|
|
1382
|
+
if (s.type === TT.COMMA) {
|
|
1383
|
+
s.scan();
|
|
1384
|
+
} else {
|
|
1385
|
+
break;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
if (s.type !== TT.CLOSE) {
|
|
1391
|
+
throw new Error(
|
|
1392
|
+
`Expected closing parenthesis for ${name}()`
|
|
1393
|
+
);
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
s.scan();
|
|
1397
|
+
|
|
1398
|
+
// Create appropriate transform component
|
|
1399
|
+
if (name === 'translate' || name === 'translate3d') {
|
|
1400
|
+
list.push(new CSSTranslate(
|
|
1401
|
+
args[0],
|
|
1402
|
+
args[1] || new CSSUnitValue(0, 'px'),
|
|
1403
|
+
args[2]
|
|
1404
|
+
));
|
|
1405
|
+
} else if (name === 'translatex') {
|
|
1406
|
+
list.push(new CSSTranslate(
|
|
1407
|
+
args[0],
|
|
1408
|
+
new CSSUnitValue(0, 'px')
|
|
1409
|
+
));
|
|
1410
|
+
} else if (name === 'translatey') {
|
|
1411
|
+
list.push(new CSSTranslate(
|
|
1412
|
+
new CSSUnitValue(0, 'px'),
|
|
1413
|
+
args[0]
|
|
1414
|
+
));
|
|
1415
|
+
} else if (name === 'translatez') {
|
|
1416
|
+
list.push(new CSSTranslate(
|
|
1417
|
+
new CSSUnitValue(0, 'px'),
|
|
1418
|
+
new CSSUnitValue(0, 'px'),
|
|
1419
|
+
args[0]
|
|
1420
|
+
));
|
|
1421
|
+
} else if (name === 'rotate' || name === 'rotate3d') {
|
|
1422
|
+
if (args.length === 1) {
|
|
1423
|
+
list.push(new CSSRotate(args[0]));
|
|
1424
|
+
} else if (args.length === 4) {
|
|
1425
|
+
list.push(new CSSRotate(args[0], args[1], args[2], args[3]));
|
|
1426
|
+
} else {
|
|
1427
|
+
throw new Error(
|
|
1428
|
+
`Invalid number of arguments for ${name}()`
|
|
1429
|
+
);
|
|
1430
|
+
}
|
|
1431
|
+
} else if (name === 'rotatex') {
|
|
1432
|
+
list.push(new CSSRotate(
|
|
1433
|
+
new CSSUnitValue(1, 'number'),
|
|
1434
|
+
new CSSUnitValue(0, 'number'),
|
|
1435
|
+
new CSSUnitValue(0, 'number'),
|
|
1436
|
+
args[0]
|
|
1437
|
+
));
|
|
1438
|
+
} else if (name === 'rotatey') {
|
|
1439
|
+
list.push(new CSSRotate(
|
|
1440
|
+
new CSSUnitValue(0, 'number'),
|
|
1441
|
+
new CSSUnitValue(1, 'number'),
|
|
1442
|
+
new CSSUnitValue(0, 'number'),
|
|
1443
|
+
args[0]
|
|
1444
|
+
));
|
|
1445
|
+
} else if (name === 'rotatez') {
|
|
1446
|
+
list.push(new CSSRotate(args[0]));
|
|
1447
|
+
} else if (name === 'scale' || name === 'scale3d') {
|
|
1448
|
+
list.push(new CSSScale(args[0], args[1], args[2]));
|
|
1449
|
+
} else if (name === 'scalex') {
|
|
1450
|
+
list.push(new CSSScale(
|
|
1451
|
+
args[0],
|
|
1452
|
+
new CSSUnitValue(1, 'number')
|
|
1453
|
+
));
|
|
1454
|
+
} else if (name === 'scaley') {
|
|
1455
|
+
list.push(new CSSScale(
|
|
1456
|
+
new CSSUnitValue(1, 'number'),
|
|
1457
|
+
args[0]
|
|
1458
|
+
));
|
|
1459
|
+
} else if (name === 'scalez') {
|
|
1460
|
+
list.push(new CSSScale(
|
|
1461
|
+
new CSSUnitValue(1, 'number'),
|
|
1462
|
+
new CSSUnitValue(1, 'number'),
|
|
1463
|
+
args[0]
|
|
1464
|
+
));
|
|
1465
|
+
} else if (name === 'skew') {
|
|
1466
|
+
list.push(new CSSSkew(
|
|
1467
|
+
args[0],
|
|
1468
|
+
args[1] || new CSSUnitValue(0, 'deg')
|
|
1469
|
+
));
|
|
1470
|
+
} else if (name === 'skewx') {
|
|
1471
|
+
list.push(new CSSSkewX(args[0]));
|
|
1472
|
+
} else if (name === 'skewy') {
|
|
1473
|
+
list.push(new CSSSkewY(args[0]));
|
|
1474
|
+
} else if (name === 'perspective') {
|
|
1475
|
+
list.push(new CSSPerspective(args[0]));
|
|
1476
|
+
} else if (name === 'matrix') {
|
|
1477
|
+
if (args.length === 6) {
|
|
1478
|
+
list.push(new CSSMatrixComponent(
|
|
1479
|
+
args[0], args[1], args[2],
|
|
1480
|
+
args[3], args[4], args[5]
|
|
1481
|
+
));
|
|
1482
|
+
} else {
|
|
1483
|
+
throw new Error("matrix() requires 6 arguments");
|
|
1484
|
+
}
|
|
1485
|
+
} else {
|
|
1486
|
+
throw new Error(`Unknown transform function: ${name}()`);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
return new CSSTransformValue(list);
|
|
1491
|
+
}
|
|
1492
|
+
};
|
|
1493
|
+
|
|
1494
|
+
// --- 8. DOM Integration ---
|
|
1495
|
+
|
|
1496
|
+
const toKebab = (prop) => {
|
|
1497
|
+
let cached = KEBAB_CACHE.get(prop);
|
|
1498
|
+
if (cached !== undefined) return cached;
|
|
1499
|
+
|
|
1500
|
+
const kebab = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
1501
|
+
KEBAB_CACHE.set(prop, kebab);
|
|
1502
|
+
return kebab;
|
|
1503
|
+
};
|
|
1504
|
+
|
|
1505
|
+
const COMMA_SEPARATED_PROPS = {
|
|
1506
|
+
'transition': 1,
|
|
1507
|
+
'animation': 1,
|
|
1508
|
+
'box-shadow': 1,
|
|
1509
|
+
'text-shadow': 1,
|
|
1510
|
+
'background': 1,
|
|
1511
|
+
'background-image': 1,
|
|
1512
|
+
'font-family': 1,
|
|
1513
|
+
'stroke-dasharray': 1,
|
|
1514
|
+
'transform': 0 // Space-separated
|
|
1515
|
+
};
|
|
1516
|
+
|
|
1517
|
+
class StylePropertyMapReadOnly {
|
|
1518
|
+
constructor(element) {
|
|
1519
|
+
if (!element || !element.style) {
|
|
1520
|
+
throw new TypeError("Element must have a style property");
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
this._el = element;
|
|
1524
|
+
this._style = element.style;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
get(prop) {
|
|
1528
|
+
const kProp = toKebab(prop);
|
|
1529
|
+
const val = this._style.getPropertyValue(kProp);
|
|
1530
|
+
|
|
1531
|
+
if (!val) return null;
|
|
1532
|
+
|
|
1533
|
+
try {
|
|
1534
|
+
return Parser.parse(kProp, val);
|
|
1535
|
+
} catch (e) {
|
|
1536
|
+
console.warn(`Moonlight: Parse error for ${kProp}:`, e.message);
|
|
1537
|
+
return new CSSUnparsedValue([val]);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
getAll(prop) {
|
|
1542
|
+
const kProp = toKebab(prop);
|
|
1543
|
+
const val = this._style.getPropertyValue(kProp);
|
|
1544
|
+
|
|
1545
|
+
if (!val) return [];
|
|
1546
|
+
|
|
1547
|
+
try {
|
|
1548
|
+
// For comma-separated properties, split and parse each
|
|
1549
|
+
if (COMMA_SEPARATED_PROPS[kProp]) {
|
|
1550
|
+
const parts = val.split(',').map(p => p.trim());
|
|
1551
|
+
return parts.map(p => {
|
|
1552
|
+
try {
|
|
1553
|
+
return Parser.parse(kProp, p);
|
|
1554
|
+
} catch (e) {
|
|
1555
|
+
return new CSSUnparsedValue([p]);
|
|
1556
|
+
}
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
return [Parser.parse(kProp, val)];
|
|
1561
|
+
} catch (e) {
|
|
1562
|
+
return [new CSSUnparsedValue([val])];
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
has(prop) {
|
|
1567
|
+
return !!this._style.getPropertyValue(toKebab(prop));
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
get size() {
|
|
1571
|
+
return this._style.length;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
*entries() {
|
|
1575
|
+
for (let i = 0; i < this._style.length; i++) {
|
|
1576
|
+
const p = this._style[i];
|
|
1577
|
+
const v = this.get(p);
|
|
1578
|
+
if (v !== null) {
|
|
1579
|
+
yield [p, v];
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
*keys() {
|
|
1585
|
+
for (let i = 0; i < this._style.length; i++) {
|
|
1586
|
+
yield this._style[i];
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
*values() {
|
|
1591
|
+
for (let i = 0; i < this._style.length; i++) {
|
|
1592
|
+
const v = this.get(this._style[i]);
|
|
1593
|
+
if (v !== null) {
|
|
1594
|
+
yield v;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
forEach(callback, thisArg) {
|
|
1600
|
+
for (let i = 0; i < this._style.length; i++) {
|
|
1601
|
+
const p = this._style[i];
|
|
1602
|
+
const v = this.get(p);
|
|
1603
|
+
if (v !== null) {
|
|
1604
|
+
callback.call(thisArg, v, p, this);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
[Symbol.iterator]() {
|
|
1610
|
+
return this.entries();
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
class StylePropertyMap extends StylePropertyMapReadOnly {
|
|
1615
|
+
set(prop, ...values) {
|
|
1616
|
+
const kProp = toKebab(prop);
|
|
1617
|
+
|
|
1618
|
+
if (values.length === 0) {
|
|
1619
|
+
throw new TypeError(
|
|
1620
|
+
"Failed to execute 'set': 1 argument required, but only 0 present."
|
|
1621
|
+
);
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
const valStr = values.map(v => {
|
|
1625
|
+
if (v && typeof v.toString === 'function') {
|
|
1626
|
+
return v.toString();
|
|
1627
|
+
}
|
|
1628
|
+
return String(v);
|
|
1629
|
+
}).join(' ');
|
|
1630
|
+
|
|
1631
|
+
this._style.setProperty(kProp, valStr);
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
append(prop, ...values) {
|
|
1635
|
+
const kProp = toKebab(prop);
|
|
1636
|
+
|
|
1637
|
+
if (values.length === 0) {
|
|
1638
|
+
throw new TypeError(
|
|
1639
|
+
"Failed to execute 'append': 1 argument required."
|
|
1640
|
+
);
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
const newPart = values.map(v => {
|
|
1644
|
+
if (v && typeof v.toString === 'function') {
|
|
1645
|
+
return v.toString();
|
|
1646
|
+
}
|
|
1647
|
+
return String(v);
|
|
1648
|
+
}).join(' ');
|
|
1649
|
+
|
|
1650
|
+
const current = this._style.getPropertyValue(kProp);
|
|
1651
|
+
|
|
1652
|
+
if (!current) {
|
|
1653
|
+
this._style.setProperty(kProp, newPart);
|
|
1654
|
+
} else {
|
|
1655
|
+
const separator = COMMA_SEPARATED_PROPS[kProp] ? ', ' : ' ';
|
|
1656
|
+
this._style.setProperty(kProp, current + separator + newPart);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
delete(prop) {
|
|
1661
|
+
const kProp = toKebab(prop);
|
|
1662
|
+
this._style.removeProperty(kProp);
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
clear() {
|
|
1666
|
+
this._style.cssText = '';
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
// Install attributeStyleMap on HTMLElement
|
|
1671
|
+
if (typeof HTMLElement !== 'undefined' &&
|
|
1672
|
+
!HTMLElement.prototype.hasOwnProperty('attributeStyleMap')) {
|
|
1673
|
+
const mapCache = new WeakMap();
|
|
1674
|
+
|
|
1675
|
+
Object.defineProperty(HTMLElement.prototype, 'attributeStyleMap', {
|
|
1676
|
+
enumerable: true,
|
|
1677
|
+
configurable: true,
|
|
1678
|
+
get() {
|
|
1679
|
+
if (!mapCache.has(this)) {
|
|
1680
|
+
mapCache.set(this, new StylePropertyMap(this));
|
|
1681
|
+
}
|
|
1682
|
+
return mapCache.get(this);
|
|
1683
|
+
}
|
|
1684
|
+
});
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
// --- 9. Exports ---
|
|
1688
|
+
|
|
1689
|
+
const exports = {
|
|
1690
|
+
// Core
|
|
1691
|
+
CSSStyleValue,
|
|
1692
|
+
CSSNumericValue,
|
|
1693
|
+
CSSUnitValue,
|
|
1694
|
+
CSSKeywordValue,
|
|
1695
|
+
CSSUnparsedValue,
|
|
1696
|
+
CSSVariableReferenceValue,
|
|
1697
|
+
|
|
1698
|
+
// Math
|
|
1699
|
+
CSSMathValue,
|
|
1700
|
+
CSSMathSum,
|
|
1701
|
+
CSSMathProduct,
|
|
1702
|
+
CSSMathNegate,
|
|
1703
|
+
CSSMathInvert,
|
|
1704
|
+
CSSMathMin,
|
|
1705
|
+
CSSMathMax,
|
|
1706
|
+
CSSMathClamp,
|
|
1707
|
+
|
|
1708
|
+
// Transform
|
|
1709
|
+
CSSTransformValue,
|
|
1710
|
+
CSSTransformComponent,
|
|
1711
|
+
CSSTranslate,
|
|
1712
|
+
CSSRotate,
|
|
1713
|
+
CSSScale,
|
|
1714
|
+
CSSSkew,
|
|
1715
|
+
CSSSkewX,
|
|
1716
|
+
CSSSkewY,
|
|
1717
|
+
CSSPerspective,
|
|
1718
|
+
CSSMatrixComponent,
|
|
1719
|
+
|
|
1720
|
+
// Maps
|
|
1721
|
+
StylePropertyMap,
|
|
1722
|
+
StylePropertyMapReadOnly
|
|
1723
|
+
};
|
|
1724
|
+
|
|
1725
|
+
for (let k in exports) global[k] ??= exports[k];
|
|
1726
|
+
|
|
1727
|
+
// CSS Namespace
|
|
1728
|
+
global.CSS = global.CSS || {};
|
|
1729
|
+
|
|
1730
|
+
// Create factory methods for all units
|
|
1731
|
+
for (let u in UNIT_MAP) u && (global.CSS[u] = v => new CSSUnitValue(v, u));
|
|
1732
|
+
})(typeof window !== 'undefined' ? window : this);
|