css-typed-om-polyfill 0.1.1 → 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/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);