css-typed-om-polyfill 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1369 @@
1
+ (function () {
2
+ 'use strict'
3
+
4
+ console.log('CSS Typed OM Polyfill: Initializing...')
5
+
6
+ // --- Helper: Simple unit definitions ---
7
+ const LENGTH_UNITS = ['px', 'em', 'rem', 'vw', 'vh', 'vmin', 'vmax', 'cm', 'mm', 'in', 'pt', 'pc', 'ch', 'ex', 'cap', 'ic', 'lh', 'rlh', 'q']
8
+ const ANGLE_UNITS = ['deg', 'rad', 'grad', 'turn']
9
+ const TIME_UNITS = ['s', 'ms']
10
+ const FREQUENCY_UNITS = ['hz', 'khz']
11
+ const RESOLUTION_UNITS = ['dpi', 'dpcm', 'dppx']
12
+ const FLEX_UNITS = ['fr']
13
+ const PERCENTAGE_UNIT = ['%']
14
+ const NUMBER_UNIT_TYPE = 'number'
15
+
16
+ const ALL_UNITS = [
17
+ ...LENGTH_UNITS, ...ANGLE_UNITS, ...TIME_UNITS,
18
+ ...FREQUENCY_UNITS, ...RESOLUTION_UNITS, ...FLEX_UNITS, PERCENTAGE_UNIT[0]
19
+ ]
20
+
21
+ function getUnitCategory(unit) {
22
+ if (typeof unit !== 'string') return 'unknown'
23
+ unit = unit.toLowerCase()
24
+ if (LENGTH_UNITS.includes(unit)) return 'length'
25
+ if (ANGLE_UNITS.includes(unit)) return 'angle'
26
+ if (TIME_UNITS.includes(unit)) return 'time'
27
+ if (FREQUENCY_UNITS.includes(unit)) return 'frequency'
28
+ if (RESOLUTION_UNITS.includes(unit)) return 'resolution'
29
+ if (FLEX_UNITS.includes(unit)) return 'flex'
30
+ if (PERCENTAGE_UNIT.includes(unit)) return 'percent'
31
+ if (unit === NUMBER_UNIT_TYPE) return NUMBER_UNIT_TYPE
32
+ return 'unknown'
33
+ }
34
+
35
+ const conversionRates = {
36
+ time: { ms: 1, s: 1000 },
37
+ angle: { deg: 1, rad: 180 / Math.PI, grad: 400 / 360, turn: 360 },
38
+ }
39
+
40
+ // --- CSSStyleValue (Base Class) ---
41
+ class CSSStyleValue {
42
+ constructor() {
43
+ if (this.constructor === CSSStyleValue) {
44
+ throw new TypeError("Illegal constructor: CSSStyleValue is an abstract base class.")
45
+ }
46
+ }
47
+ }
48
+
49
+ // --- Simple Numeric Value Parser ---
50
+ function parseSimpleCssNumericValue(text) {
51
+ text = text.trim()
52
+ const unitMatch = text.match(/^(-?(?:\d*\.\d+|\d+\.?\d*))(%|[a-zA-Z]+)?$/)
53
+ if (unitMatch) {
54
+ const value = parseFloat(unitMatch[1])
55
+ if (!isFinite(value)) return null
56
+ const unit = unitMatch[2] ? unitMatch[2].toLowerCase() : undefined
57
+
58
+ if (unit === undefined) return new CSSNumericValue(value, NUMBER_UNIT_TYPE)
59
+ if (unit === '%') return new CSSUnitValue(value, '%')
60
+ if (ALL_UNITS.includes(unit)) return new CSSUnitValue(value, unit)
61
+ return null
62
+ // Unknown unit
63
+ }
64
+ if (/^(-?(?:\d*\.\d+|\d+\.?\d*))$/.test(text)) {
65
+ const value = parseFloat(text)
66
+ if (isFinite(value)) return new CSSNumericValue(value, NUMBER_UNIT_TYPE)
67
+ }
68
+ return null
69
+ }
70
+
71
+ // --- Parenthesis Matcher ---
72
+ function findMatchingParen(str, startIndex = 0) {
73
+ let level = 0
74
+ if (str[startIndex] !== '(') return -1
75
+ for (let i = startIndex; i < str.length; i++) {
76
+ if (str[i] === '(') level++
77
+ else if (str[i] === ')') {
78
+ level--
79
+ if (level === 0) return i
80
+ }
81
+ }
82
+ return -1
83
+ }
84
+
85
+ // --- Function Argument Parser ---
86
+ function parseFunctionArguments(argsString, functionName = 'function') {
87
+ argsString = argsString.trim()
88
+ if (argsString === '') return []
89
+ const args = []
90
+ let currentArg = ''
91
+ let parenDepth = 0
92
+ for (let i = 0; i < argsString.length; i++) {
93
+ const char = argsString[i]
94
+ if (char === '(') {
95
+ parenDepth++
96
+ currentArg += char
97
+ }
98
+ else if (char === ')') {
99
+ currentArg += char
100
+ parenDepth = Math.max(0, parenDepth - 1)
101
+ } else if (char === ',' && parenDepth === 0) {
102
+ const argTrimmed = currentArg.trim()
103
+ if (argTrimmed === '') throw new Error(`Empty argument found in ${functionName}()`)
104
+ args.push(parseCssMathExpression(argTrimmed))
105
+ // Use MATH parser
106
+ currentArg = ''
107
+ } else {
108
+ currentArg += char
109
+ }
110
+ }
111
+ const lastArgTrimmed = currentArg.trim()
112
+ if (lastArgTrimmed === '') {
113
+ if (argsString.endsWith(',')) throw new Error(`Trailing comma in ${functionName}()`)
114
+ if (args.length === 0) throw new Error(`No arguments provided to ${functionName}()`)
115
+ } else {
116
+ args.push(parseCssMathExpression(lastArgTrimmed))
117
+ // Use MATH parser
118
+ }
119
+ return args
120
+ }
121
+
122
+
123
+ // --- Recursive CSS Math Expression Parser ---
124
+ function parseCssMathExpression(expression) {
125
+ expression = expression.trim()
126
+
127
+ // 1. Outermost Parentheses
128
+ if (expression.startsWith('(') && findMatchingParen(expression, 0) === expression.length - 1) {
129
+ return parseCssMathExpression(expression.slice(1, -1))
130
+ }
131
+
132
+ // 2. Addition/Subtraction (right-to-left)
133
+ let parenLevel = 0
134
+ for (let i = expression.length - 1; i >= 0; i--) {
135
+ const char = expression[i]
136
+ if (char === ')') parenLevel++
137
+ else if (char === '(') parenLevel--
138
+ else if (parenLevel === 0 && (char === '+' || char === '-')) {
139
+ const prevChar = expression[i - 1]
140
+ const nextChar = expression[i + 1]
141
+ // CSS requires spaces around binary +/- for calc()
142
+ if (/\s/.test(prevChar) || /\s/.test(nextChar)) {
143
+ const left = expression.slice(0, i)
144
+ const right = expression.slice(i + 1)
145
+ if (left.trim() !== '' && right.trim() !== '') {
146
+ // Check it's not unary following another operator/paren/start
147
+ let prevNonSpaceIndex = i - 1
148
+ while (prevNonSpaceIndex >= 0 && /\s/.test(expression[prevNonSpaceIndex])) prevNonSpaceIndex--
149
+ if (prevNonSpaceIndex >= 0 && !['(', '+', '-', '*', '/'].includes(expression[prevNonSpaceIndex])) {
150
+ // Binary operator confirmed
151
+ const leftVal = parseCssMathExpression(left)
152
+ // Might be VarRef
153
+ const rightVal = parseCssMathExpression(right)
154
+ // Might be VarRef
155
+ if (char === '+') return leftVal.add(rightVal)
156
+ // Use .add()
157
+ if (char === '-') return leftVal.sub(rightVal)
158
+ // Use .sub()
159
+ }
160
+ } else {
161
+ throw new Error(`Invalid syntax: Missing operand around operator '${char}' in "${expression}"`)
162
+ }
163
+ } // else: likely unary or invalid syntax like "10px-5px"
164
+ }
165
+ }
166
+
167
+ // 3. Multiplication/Division (right-to-left)
168
+ parenLevel = 0
169
+ for (let i = expression.length - 1; i >= 0; i--) {
170
+ const char = expression[i]
171
+ if (char === ')') parenLevel++
172
+ else if (char === '(') parenLevel--
173
+ else if (parenLevel === 0 && (char === '*' || char === '/')) {
174
+ const left = expression.slice(0, i)
175
+ const right = expression.slice(i + 1)
176
+ if (left.trim() === '' || right.trim() === '') {
177
+ throw new Error(`Invalid syntax: Missing operand around operator '${char}' in "${expression}"`)
178
+ }
179
+ const leftVal = parseCssMathExpression(left)
180
+ const rightVal = parseCssMathExpression(right)
181
+ if (char === '*') return leftVal.mul(rightVal)
182
+ // Use .mul()
183
+ if (char === '/') return leftVal.div(rightVal)
184
+ // Use .div()
185
+ }
186
+ }
187
+
188
+ // 4. Unary Minus/Plus
189
+ if (expression.startsWith('-')) {
190
+ const operand = parseCssMathExpression(expression.slice(1))
191
+ return operand.negate()
192
+ }
193
+ if (expression.startsWith('+')) {
194
+ return parseCssMathExpression(expression.slice(1))
195
+ }
196
+
197
+ // 5. Functions (min, max, var, etc. as terms within calc)
198
+ const funcMatch = expression.match(/^([a-zA-Z-]+)\((.*)\)$/i)
199
+ if (funcMatch) {
200
+ const funcName = funcMatch[1].toLowerCase()
201
+ const funcContent = funcMatch[2]
202
+ switch (funcName) {
203
+ case 'min':
204
+ case 'max':
205
+ try {
206
+ const args = parseFunctionArguments(funcContent, funcName)
207
+ args.forEach(arg => {
208
+ if (!(arg instanceof CSSNumericValue || arg instanceof CSSVariableReferenceValue))
209
+ throw new Error(`Invalid argument type ${arg?.constructor?.name}`)
210
+ })
211
+ if (args.length === 0)
212
+ throw new Error(`No arguments for ${funcName}`)
213
+ return funcName === 'min' ? new CSSMathMin(...args) : new CSSMathMax(...args)
214
+ } catch (e) {
215
+ throw new Error(`Failed to parse ${funcName} args: ${e.message}`)
216
+ }
217
+ case 'clamp':
218
+ throw new Error(`clamp() not supported`)
219
+ case 'var':
220
+ const varParts = funcContent.match(/^\s*(--[a-zA-Z0-9_-]+)\s*(?:,\s*(.+)\s*)?$/)
221
+ if (varParts) {
222
+ const fallback = varParts[2] ? new CSSUnparsedValue([varParts[2].trim()]) : null
223
+ return new CSSVariableReferenceValue(varParts[1], fallback)
224
+ } else {
225
+ throw new Error(`Invalid var() syntax: "${expression}"`)
226
+ }
227
+ case 'calc':
228
+ return parseCssMathExpression(funcContent)
229
+ // Nested calc
230
+ default:
231
+ throw new Error(`Unsupported function "${funcName}()" in math expression.`)
232
+ }
233
+ }
234
+
235
+ // 6. Base Case: Simple numeric value
236
+ const simpleValue = parseSimpleCssNumericValue(expression)
237
+ if (simpleValue) return simpleValue
238
+
239
+ // 7. Invalid Term
240
+ throw new Error(`Invalid or unsupported term in math expression: "${expression}"`)
241
+ }
242
+
243
+
244
+ // --- CSSNumericValue (Base for numbers, units, math) ---
245
+ class CSSNumericValue extends CSSStyleValue {
246
+ constructor(value, unitType) {
247
+ super()
248
+ this._value = (unitType === NUMBER_UNIT_TYPE) ? value : NaN
249
+ this._unitType = unitType
250
+ }
251
+
252
+ type() {
253
+ const types = { length: 0, angle: 0, time: 0, frequency: 0, resolution: 0, flex: 0, percent: 0 }
254
+ const key = this._unitType
255
+ if (key === NUMBER_UNIT_TYPE) { /* all 0 */ }
256
+ else if (key === 'percent') types.percent = 1
257
+ else if (types.hasOwnProperty(key)) types[key] = 1
258
+ else if (key !== 'mixed' && key !== 'unknown')
259
+ console.warn(`CSSNumericValue.type(): Unknown unit type category "${key}"`)
260
+ return types
261
+ }
262
+
263
+ _toNumericValue(val, opName) {
264
+ if (val instanceof CSSNumericValue || val instanceof CSSVariableReferenceValue) return val
265
+ if (typeof val === 'number') return new CSSNumericValue(val, NUMBER_UNIT_TYPE)
266
+ if (typeof val === 'string') {
267
+ try {
268
+ const parsed = CSSStyleValue.parse('', val)
269
+ // Use main parser
270
+ if (parsed instanceof CSSNumericValue ||
271
+ parsed instanceof CSSVariableReferenceValue
272
+ ) return parsed
273
+ throw new TypeError(`String "${val}" parsed to non-numeric type "${parsed?.constructor?.name}" for ${opName}.`)
274
+ } catch (e) {
275
+ throw new TypeError(`Could not parse string "${val}" for ${opName}: ${e.message}`)
276
+ }
277
+ }
278
+ throw new TypeError(`Cannot use value of type ${typeof val} in numeric ${opName}.`)
279
+ }
280
+
281
+ // --- Arithmetic Methods ---
282
+ // These methods handle CSSVariableReferenceValue by creating Math objects.
283
+ add(...values) {
284
+ let result = this
285
+ for (const rawVal of values) {
286
+ const other = this._toNumericValue(rawVal, 'add')
287
+ if (result instanceof CSSMathValue || other instanceof CSSMathValue) {
288
+ const left = (result instanceof CSSMathSum || result instanceof CSSMathProduct) ? result.values : [result]
289
+ const right = (other instanceof CSSMathSum || other instanceof CSSMathProduct) ? other.values : [other]
290
+ result = new CSSMathSum([...left, ...right])
291
+ continue
292
+ }
293
+
294
+ if (result instanceof CSSUnitValue && other instanceof CSSUnitValue) {
295
+ if (result.unit === other.unit)
296
+ result = new CSSUnitValue(result.value + other.value, result.unit)
297
+ else {
298
+ const cat1 = getUnitCategory(result.unit)
299
+ const cat2 = getUnitCategory(other.unit)
300
+ const conv = ['length', 'angle', 'time', 'frequency', 'resolution']
301
+ if (cat1 !== 'unknown' && cat1 === cat2 && conv.includes(cat1)) {
302
+ try {
303
+ const convOther = other.to(result.unit)
304
+ result = new CSSUnitValue(result.value + convOther.value, result.unit)
305
+ }
306
+ catch (e) {
307
+ result = new CSSMathSum([result, other])
308
+ }
309
+ } else result = new CSSMathSum([result, other])
310
+ }
311
+ } else
312
+ result = new CSSMathSum([result, other])
313
+ }
314
+ return result
315
+ }
316
+ sub(...values) {
317
+ let result = this
318
+ for (const rawVal of values) {
319
+ const other = this._toNumericValue(rawVal, 'sub')
320
+ // Use add(negate(other)) strategy
321
+ result = result.add(other.negate())
322
+ }
323
+ return result
324
+ }
325
+ mul(...values) {
326
+ let result = this
327
+ for (const rawVal of values) {
328
+ const other = this._toNumericValue(rawVal, 'mul')
329
+ if (result instanceof CSSMathValue || other instanceof CSSMathValue) {
330
+ const left = (result instanceof CSSMathProduct) ? result.values : [result]
331
+ const right = (other instanceof CSSMathProduct) ? other.values : [other]
332
+ result = new CSSMathProduct([...left, ...right])
333
+ continue
334
+ }
335
+ const typeR = result.type()
336
+ const typeO = other.type()
337
+ const isRUnitless = Object.values(typeR).every(v => v === 0)
338
+ const isOUnitless = Object.values(typeO).every(v => v === 0)
339
+ let direct = null
340
+ if (result instanceof CSSUnitValue && isOUnitless && other instanceof CSSNumericValue)
341
+ direct = new CSSUnitValue(result.value * other._value, result.unit)
342
+ else if (other instanceof CSSUnitValue && isRUnitless && result instanceof CSSNumericValue)
343
+ direct = new CSSUnitValue(result._value * other.value, other.unit)
344
+ else if (isRUnitless && isOUnitless && result instanceof CSSNumericValue && other instanceof CSSNumericValue)
345
+ direct = new CSSNumericValue(result._value * other._value, NUMBER_UNIT_TYPE)
346
+ if (direct)
347
+ result = direct
348
+ else {
349
+ const left = (result instanceof CSSMathProduct) ? result.values : [result]
350
+ const right = (other instanceof CSSMathProduct) ? other.values : [other]
351
+ result = new CSSMathProduct([...left, ...right])
352
+ }
353
+ }
354
+ return result
355
+ }
356
+ div(...values) {
357
+ let result = this
358
+ for (const rawVal of values) {
359
+ const other = this._toNumericValue(rawVal, 'div')
360
+ // Check for division by zero early if possible (non-var)
361
+ const isZero = (other instanceof CSSUnitValue && other.value === 0) ||
362
+ (other instanceof CSSNumericValue && other._unitType === NUMBER_UNIT_TYPE && other._value === 0)
363
+ if (isZero) throw new RangeError("Division by zero in CSS calculation.")
364
+ // Use mul(invert(other)) strategy
365
+ result = result.mul(other.invert())
366
+ }
367
+ return result
368
+ }
369
+
370
+ // --- Structural Operations ---
371
+ negate() {
372
+ if (this instanceof CSSMathNegate) return this.value
373
+ // -(-x) -> x
374
+ if (this instanceof CSSUnitValue) return new CSSUnitValue(-this.value, this.unit)
375
+ if (this instanceof CSSNumericValue && this._unitType === NUMBER_UNIT_TYPE) return new CSSNumericValue(-this._value, NUMBER_UNIT_TYPE)
376
+ return new CSSMathNegate(this)
377
+ // Wrap others (including VarRef)
378
+ }
379
+ invert() {
380
+ if (this instanceof CSSMathInvert) return this.value
381
+ // 1/(1/x) -> x
382
+ // Check for inversion of zero
383
+ if ((this instanceof CSSUnitValue && this.value === 0) || (this instanceof CSSNumericValue && this._unitType === NUMBER_UNIT_TYPE && this._value === 0)) {
384
+ throw new RangeError("Division by zero (inversion of 0).")
385
+ }
386
+ if (this instanceof CSSNumericValue && this._unitType === NUMBER_UNIT_TYPE) return new CSSNumericValue(1 / this._value, NUMBER_UNIT_TYPE)
387
+ return new CSSMathInvert(this)
388
+ // Wrap others (UnitValue, VarRef, Math)
389
+ }
390
+
391
+ // --- Comparison / Conversion ---
392
+ min(...values) {
393
+ const all = [this, ...values].map(v => this._toNumericValue(v, 'min'))
394
+ return new CSSMathMin(...all)
395
+ }
396
+ max(...values) {
397
+ const all = [this, ...values].map(v => this._toNumericValue(v, 'max'))
398
+ return new CSSMathMax(...all)
399
+ }
400
+ equals(...values) { /* ... Simplified structural check ... */
401
+ for (const rawVal of values) {
402
+ let other
403
+ if (rawVal instanceof CSSNumericValue || rawVal instanceof CSSVariableReferenceValue) other = rawVal
404
+ else if (typeof rawVal === 'string' || typeof rawVal === 'number') {
405
+ try {
406
+ other = this._toNumericValue(rawVal, 'equals')
407
+ } catch (e) {
408
+ return false
409
+ }
410
+ }
411
+ else return false
412
+ // Cannot compare other types
413
+
414
+ // Crude string comparison (unreliable for math) + zero check
415
+ if (this.toString() !== other.toString()) {
416
+ const isThisZero = (this instanceof CSSUnitValue && this.value === 0) || (this instanceof CSSNumericValue && this._unitType === NUMBER_UNIT_TYPE && this._value === 0)
417
+ const isOtherZero = (other instanceof CSSUnitValue && other.value === 0) || (other instanceof CSSNumericValue && other._unitType === NUMBER_UNIT_TYPE && other._value === 0)
418
+ if (!(isThisZero && isOtherZero)) return false
419
+ }
420
+ }
421
+ return true
422
+ }
423
+ to(targetUnit) {
424
+ if (!(this instanceof CSSUnitValue)) throw new TypeError(".to() only supported on CSSUnitValue")
425
+ targetUnit = targetUnit.toLowerCase()
426
+ if (this.unit === targetUnit) return new CSSUnitValue(this.value, this.unit)
427
+ const cat1 = getUnitCategory(this.unit)
428
+ const cat2 = getUnitCategory(targetUnit)
429
+ if (cat1 === 'unknown' || cat2 === 'unknown' || cat1 !== cat2) throw new TypeError(`Cannot convert from "${this.unit}" to "${targetUnit}". Incompatible categories.`)
430
+ if (cat1 === NUMBER_UNIT_TYPE || cat2 === NUMBER_UNIT_TYPE || cat1 === 'percent' || cat2 === 'percent') throw new TypeError(`Cannot convert between units and number/percent using .to().`)
431
+ if (conversionRates[cat1]) {
432
+ const rates = conversionRates[cat1]
433
+ if (rates[this.unit] === undefined || rates[targetUnit] === undefined) throw new TypeError(`Conversion between "${this.unit}" and "${targetUnit}" not supported by polyfill rates.`)
434
+ const valueInBase = this.value * rates[this.unit]
435
+ const convertedValue = valueInBase / rates[targetUnit]
436
+ return new CSSUnitValue(convertedValue, targetUnit)
437
+ }
438
+ throw new TypeError(`Conversion from "${this.unit}" to "${targetUnit}" (category "${cat1}") is not supported by this polyfill.`)
439
+ }
440
+ toSum(...values) {
441
+ if (values.length > 0) throw new TypeError(".toSum() does not accept arguments.")
442
+ return (this instanceof CSSMathSum) ? this : new CSSMathSum([this])
443
+ }
444
+
445
+ // --- Default toString ---
446
+ toString() {
447
+ if (this._unitType === NUMBER_UNIT_TYPE) return String(this._value)
448
+ console.warn("CSSNumericValue.toString() called on base/unexpected type:", this.constructor.name)
449
+ return 'calc(?)'
450
+ }
451
+ static parse(cssText) {
452
+ const p = CSSStyleValue.parse('', cssText.trim())
453
+ if (p instanceof CSSNumericValue) return p
454
+ if (p instanceof CSSVariableReferenceValue) throw new TypeError(`Input "${cssText}" parsed as a CSSVariableReferenceValue.`)
455
+ throw new TypeError(`Could not parse "${cssText}" as a CSSNumericValue (parsed as ${p?.constructor?.name || 'unknown'}).`)
456
+ }
457
+ }
458
+
459
+ // --- CSSUnitValue ---
460
+ class CSSUnitValue extends CSSNumericValue {
461
+ constructor(value, unit) {
462
+ if (typeof value !== 'number' || !isFinite(value)) throw new TypeError("Value must be finite number.")
463
+ if (typeof unit !== 'string' || unit === '') throw new TypeError("Unit must be non-empty string.")
464
+ const unitLower = unit.toLowerCase()
465
+ const category = getUnitCategory(unitLower)
466
+ if (category === NUMBER_UNIT_TYPE) throw new TypeError(`Cannot create CSSUnitValue with unit type number.`)
467
+ super(value, category)
468
+ // Pass category to parent
469
+ if (category === 'unknown') console.warn(`CSSUnitValue: Created value with unknown unit "${unit}".`)
470
+ this._internalValue = value
471
+ this._unit = unitLower
472
+ }
473
+ get value() {
474
+ return this._internalValue
475
+ }
476
+ set value(v) {
477
+ if (typeof v !== 'number' || !isFinite(v)) throw new TypeError("Value must be finite number.")
478
+ this._internalValue = v
479
+ }
480
+ get unit() {
481
+ return this._unit
482
+ }
483
+ toString() {
484
+ return `${this.value}${this.unit}`
485
+ }
486
+ }
487
+
488
+ // --- CSSKeywordValue ---
489
+ class CSSKeywordValue extends CSSStyleValue {
490
+ constructor(value) {
491
+ super()
492
+ if (typeof value !== 'string' || value.trim() === '') throw new TypeError("Keyword value must be non-empty string.")
493
+ // Relaxed identifier check
494
+ if (!/^-?[_a-zA-Z]/.test(value) && !['inherit', 'initial', 'unset', 'revert', 'auto', 'none'].includes(value.toLowerCase())) {
495
+ console.warn(`CSSKeywordValue: Value "${value}" might not be a valid CSS keyword.`)
496
+ }
497
+ this._value = value
498
+ }
499
+ get value() {
500
+ return this._value
501
+ }
502
+ set value(v) {
503
+ if (typeof v !== 'string' || v.trim() === '') throw new TypeError("Keyword value must be non-empty string.")
504
+ this._value = v
505
+ }
506
+ toString() {
507
+ return this.value
508
+ }
509
+ }
510
+
511
+ // --- CSSUnparsedValue ---
512
+ // (Declaration moved up for forward reference)
513
+ class CSSUnparsedValue extends CSSStyleValue {
514
+ constructor(members = []) {
515
+ super()
516
+ if (!Array.isArray(members))
517
+ throw new TypeError("CSSUnparsedValue needs an array.")
518
+ if (!members.every(m => typeof m === 'string' || m instanceof CSSVariableReferenceValue)) {
519
+ const invalid = members.find(m => typeof m !== 'string' && !(m instanceof CSSVariableReferenceValue))
520
+ throw new TypeError(`CSSUnparsedValue members must be strings or CSSVariableReferenceValue. Found: ${invalid?.constructor?.name || typeof invalid}`)
521
+ }
522
+ this._members = Object.freeze([...members])
523
+ }
524
+ [Symbol.iterator]() {
525
+ return this._members[Symbol.iterator]()
526
+ }
527
+ get length() { return this._members.length }
528
+ item(i) { return this._members[i] }
529
+ toString() { return this._members.map(m => String(m)).join('') }
530
+ // Implement array-like readonly properties/methods
531
+ entries() { return this._members.entries() }
532
+ forEach(callback, thisArg) { this._members.forEach(callback, thisArg) }
533
+ keys() { return this._members.keys() }
534
+ values() { return this._members.values() }
535
+ }
536
+
537
+ // --- CSSVariableReferenceValue ---
538
+ class CSSVariableReferenceValue extends CSSStyleValue { // Doesn't inherit CSSNumericValue!
539
+ constructor(variable, fallback = null) {
540
+ super()
541
+ if (typeof variable !== 'string' || !variable.startsWith('--'))
542
+ throw new TypeError("Variable name must start with '--'.")
543
+ if (fallback !== null && !(fallback instanceof CSSUnparsedValue))
544
+ throw new TypeError("Fallback must be CSSUnparsedValue or null.")
545
+ this.variable = variable.trim()
546
+ this.fallback = fallback
547
+ }
548
+ toString() {
549
+ return `var(${this.variable}${this.fallback ? `, ${this.fallback.toString()}` : ''})`
550
+ }
551
+ type() {
552
+ console.warn("CSSVariableReferenceValue.type(): Type is indeterminate.")
553
+ return {}
554
+ }
555
+
556
+ // *** FIX: Add delegating arithmetic methods to provide the interface needed by the parser ***
557
+ _toNumericValue(...values) { return CSSNumericValue.prototype._toNumericValue.call(this, ...values) }
558
+ add(...values) { return CSSNumericValue.prototype.add.call(this, ...values) }
559
+ sub(...values) { return CSSNumericValue.prototype.sub.call(this, ...values) }
560
+ mul(...values) { return CSSNumericValue.prototype.mul.call(this, ...values) }
561
+ div(...values) { return CSSNumericValue.prototype.div.call(this, ...values) }
562
+ min(...values) { return CSSNumericValue.prototype.min.call(this, ...values) }
563
+ max(...values) { return CSSNumericValue.prototype.max.call(this, ...values) }
564
+ negate() { return CSSNumericValue.prototype.negate.call(this) }
565
+ invert() { return CSSNumericValue.prototype.invert.call(this) }
566
+ // Add equals/to/toSum? The parser uses add/sub/mul/div/negate/invert primarily.
567
+ // Let's skip adding the others unless needed, as VarRef isn't truly numeric itself.
568
+ }
569
+
570
+ // --- CSSMathValue Base ---
571
+ class CSSMathValue extends CSSNumericValue {
572
+ constructor() {
573
+ super(NaN, 'mixed')
574
+ if (this.constructor === CSSMathValue)
575
+ throw new TypeError("CSSMathValue is abstract.")
576
+ this._operands = []
577
+ }
578
+ get values() { return this._operands }
579
+ // Helper for formatting operands in toString()
580
+ _formatOperand(op, context = 'sum') { // context 'sum' or 'product'
581
+ let opStr = op.toString()
582
+ let needsParens = false
583
+ // Parenthesize lower precedence operations, or functions
584
+ if (context === 'sum' && (op instanceof CSSMathSum)) needsParens = true
585
+ // e.g. a + (b+c)
586
+ if (context === 'product' && (op instanceof CSSMathSum)) needsParens = true
587
+ // e.g. a * (b+c)
588
+ // Always wrap nested calc/min/max/clamp? Safer.
589
+ if (opStr.startsWith('calc(') || opStr.startsWith('min(') || opStr.startsWith('max(') || opStr.startsWith('clamp(')) {
590
+ needsParens = true
591
+ if (opStr.startsWith('calc(')) opStr = opStr.slice(5, -1).trim()
592
+ }
593
+ // Parenthesize negation if it's not a simple negative number/unit
594
+ if (op instanceof CSSMathNegate && !(op.value instanceof CSSUnitValue || (op.value instanceof CSSNumericValue && op.value._unitType === NUMBER_UNIT_TYPE))) {
595
+ needsParens = true
596
+ }
597
+ // Parenthesize inversion if it's not a simple number inversion
598
+ if (op instanceof CSSMathInvert && !(op.value instanceof CSSNumericValue && op.value._unitType === NUMBER_UNIT_TYPE)) {
599
+ needsParens = true
600
+ }
601
+
602
+ if (needsParens) {
603
+ // Check again if simple after stripping calc()
604
+ return (opStr.includes(' ') || opStr.includes('(') || opStr.includes(',')) ? `(${opStr})` : opStr
605
+ }
606
+ return opStr
607
+ }
608
+ }
609
+
610
+ class CSSMathSum extends CSSMathValue {
611
+ constructor(operands) {
612
+ super()
613
+ if (!Array.isArray(operands) || operands.length === 0)
614
+ throw new TypeError("CSSMathSum needs operands.")
615
+ this._operands = Object.freeze(operands.map((op, i) => {
616
+ if (op instanceof CSSNumericValue || op instanceof CSSVariableReferenceValue) return op
617
+ throw new TypeError(`CSSMathSum operand ${i + 1} invalid type ${op?.constructor?.name}`)
618
+ }))
619
+ }
620
+ get operator() { return "sum" }
621
+ toString() {
622
+ if (this.values.length === 0) return 'calc(0)'
623
+ let result = ''
624
+ for (let i = 0; i < this.values.length; i++) {
625
+ const op = this.values[i]
626
+ let sign = ' + '
627
+ let valueToFormat = op
628
+ if (op instanceof CSSMathNegate) {
629
+ sign = ' - '
630
+ valueToFormat = op.value
631
+ }
632
+ if (i === 0 && sign === ' - ') result += '-' + this._formatOperand(valueToFormat, 'sum')
633
+ else {
634
+ if (i > 0) result += sign
635
+ result += this._formatOperand(valueToFormat, 'sum')
636
+ }
637
+ }
638
+ return `calc(${result})`
639
+ }
640
+ // Refined type compatibility check
641
+ type() {
642
+ if (this._operands.length === 0) return super.type()
643
+ let commonType = null
644
+ let hasVar = false
645
+ let hasUnknown = false
646
+ for (const op of this._operands) {
647
+ let opType
648
+ if (op instanceof CSSVariableReferenceValue) {
649
+ hasVar = true
650
+ opType = {}
651
+ /* Treat var as empty type for check */
652
+ }
653
+ else if (op instanceof CSSNumericValue) {
654
+ opType = op.type()
655
+ }
656
+ else {
657
+ hasUnknown = true
658
+ continue
659
+ } // Should not happen if constructor validated
660
+
661
+ // Skip check if operand is var() - just note its presence
662
+ const isOpVar = (op instanceof CSSVariableReferenceValue)
663
+
664
+ if (Object.keys(opType).length === 0 && !isOpVar && !(op instanceof CSSNumericValue && op._unitType === NUMBER_UNIT_TYPE)) {
665
+ hasUnknown = true
666
+ continue
667
+ }
668
+
669
+ if (!commonType && !hasUnknown) {
670
+ commonType = { ...opType }
671
+ } // Initialize
672
+ else if (!hasUnknown) {
673
+ // Check compatibility with commonType, allowing var() presence
674
+ const currentKeys = Object.keys(commonType).filter(k => commonType[k] !== 0)
675
+ const opKeys = Object.keys(opType).filter(k => opType[k] !== 0)
676
+ const currentIsVarPlaceholder = currentKeys.length === 0 && hasVar
677
+ // Is commonType just from previous vars?
678
+
679
+ if (isOpVar) continue
680
+ // Don't check compatibility against var() itself
681
+
682
+ let compatible = false
683
+ // Check against established non-var commonType
684
+ const commonCategory = currentKeys.find(k => k !== 'percent')
685
+ const opCategory = opKeys.find(k => k !== 'percent')
686
+
687
+ if (!commonCategory && !opCategory) compatible = true
688
+ // number/percent + number/percent
689
+ else if (commonCategory === 'length' && !opCategory) compatible = true
690
+ // length + number/percent
691
+ else if (!commonCategory && opCategory === 'length') compatible = true
692
+ // number/percent + length
693
+ else if (commonCategory && commonCategory === opCategory) compatible = true
694
+ // unit + same unit (potentially + percent)
695
+ // else: incompatible categories
696
+
697
+ if (compatible) {
698
+ // Update commonType to reflect mix if needed (e.g., adding percent)
699
+ if (!commonType.length && opType.length) commonType.length = opType.length
700
+ if (!commonType.percent && opType.percent) commonType.percent = opType.percent
701
+ // Update other types similarly if applicable
702
+ } else {
703
+ console.error(`CSSMathSum incompatible additive types: {${currentKeys.join()}} + {${opKeys.join()}}. Expr:`, this.toString())
704
+ return {}
705
+ // Incompatible mix
706
+ }
707
+ }
708
+ }
709
+ if (hasUnknown && !commonType) return {}
710
+ // Only unknowns
711
+ if (hasVar) console.warn("CSSMathSum.type(): Type includes var(), result indeterminate.")
712
+ return commonType || {}
713
+ // Return combined type (or empty if only vars/unknowns)
714
+ }
715
+ }
716
+
717
+ // --- CSSMathProduct ---
718
+ class CSSMathProduct extends CSSMathValue {
719
+ constructor(operands) {
720
+ super()
721
+ if (!Array.isArray(operands) || operands.length === 0) throw new TypeError("CSSMathProduct needs operands.")
722
+ this._operands = Object.freeze(operands.map((op, i) => {
723
+ if (op instanceof CSSNumericValue || op instanceof CSSVariableReferenceValue) return op
724
+ throw new TypeError(`CSSMathProduct operand ${i + 1} invalid type ${op?.constructor?.name}`)
725
+ }))
726
+ }
727
+ get operator() {
728
+ return "product"
729
+ }
730
+ toString() {
731
+ const numTerms = []
732
+ const denTerms = []
733
+ this.values.forEach(op => {
734
+ let valueToFormat = op
735
+ let isDen = false
736
+ if (op instanceof CSSMathInvert) {
737
+ isDen = true
738
+ valueToFormat = op.value
739
+ }
740
+ const termStr = this._formatOperand(valueToFormat, 'product')
741
+ // Use product context
742
+ if (isDen) denTerms.push(termStr)
743
+ else numTerms.push(termStr)
744
+ })
745
+ const numStr = numTerms.length === 0 ? '1' : numTerms.join(' * ')
746
+ let result = numStr
747
+ if (denTerms.length > 0) {
748
+ const denStr = denTerms.join(' * ')
749
+ // Wrap denominator if it has spaces or is a function call, indicating complexity or multiple terms
750
+ const wrapDen = denTerms.length > 1 || denStr.includes(' ') || denStr.includes('(')
751
+ result += ` / ${wrapDen ? `(${denStr})` : denStr}`
752
+ }
753
+ return `calc(${result})`
754
+ }
755
+ type() {
756
+ const combined = { length: 0, angle: 0, time: 0, frequency: 0, resolution: 0, flex: 0, percent: 0 }
757
+ let hasVar = false
758
+ let hasUnknown = false
759
+ this._operands.forEach(op => {
760
+ if (op instanceof CSSVariableReferenceValue) {
761
+ hasVar = true
762
+ return
763
+ }
764
+ let effType
765
+ let isInverted = op instanceof CSSMathInvert
766
+ const baseVal = isInverted ? op.value : op
767
+ if (baseVal instanceof CSSVariableReferenceValue) {
768
+ hasVar = true
769
+ return
770
+ }
771
+ if (!(baseVal instanceof CSSNumericValue)) {
772
+ hasUnknown = true
773
+ return
774
+ }
775
+ effType = baseVal.type()
776
+ if (Object.keys(effType).length === 0 && !(baseVal instanceof CSSNumericValue && baseVal._unitType === NUMBER_UNIT_TYPE)) {
777
+ hasUnknown = true
778
+ return
779
+ }
780
+
781
+ for (const key in effType) {
782
+ if (combined.hasOwnProperty(key)) combined[key] += (isInverted ? -1 : 1) * effType[key]
783
+ else {
784
+ hasUnknown = true
785
+ } // Should not happen
786
+ }
787
+ })
788
+ if (hasVar) console.warn("CSSMathProduct.type(): Type includes var(), result indeterminate.")
789
+ if (hasUnknown) console.warn("CSSMathProduct.type(): Operands included unknown types.")
790
+ // Basic percentage simplification: L * % -> L etc.
791
+ if (combined.percent === 1) {
792
+ const otherDims = Object.keys(combined).filter(k => k !== 'percent' && combined[k] === 1)
793
+ if (otherDims.length === 1 && Object.values(combined).filter(v => v !== 0).length === 2) {
794
+ combined.percent = 0
795
+ // Remove percent if combined like L^1 * %^1
796
+ }
797
+ }
798
+ return combined
799
+ }
800
+ }
801
+
802
+ // --- CSSMathNegate ---
803
+ class CSSMathNegate extends CSSMathValue {
804
+ constructor(value) {
805
+ super()
806
+ if (!(value instanceof CSSNumericValue || value instanceof CSSVariableReferenceValue)) throw new TypeError("CSSMathNegate needs CSSNumericValue or CSSVariableReferenceValue.")
807
+ this._value = value
808
+ this._operands = Object.freeze([value])
809
+ }
810
+ get operator() {
811
+ return "negate"
812
+ }
813
+ get value() {
814
+ return this._value
815
+ }
816
+ toString() {
817
+ return `calc(-1 * ${this._formatOperand(this._value, 'product')})`
818
+ } // Use format helper
819
+ type() {
820
+ return this._value.type()
821
+ } // Type delegates to inner value
822
+ }
823
+
824
+ // --- CSSMathInvert ---
825
+ class CSSMathInvert extends CSSMathValue {
826
+ constructor(value) {
827
+ super()
828
+ if (!(value instanceof CSSNumericValue || value instanceof CSSVariableReferenceValue)) throw new TypeError("CSSMathInvert needs CSSNumericValue or CSSVariableReferenceValue.")
829
+ // Check zero early
830
+ if ((value instanceof CSSUnitValue && value.value === 0) || (value instanceof CSSNumericValue && value._unitType === NUMBER_UNIT_TYPE && value._value === 0)) {
831
+ throw new RangeError("Division by zero (inversion of 0).")
832
+ }
833
+ this._value = value
834
+ this._operands = Object.freeze([value])
835
+ }
836
+ get operator() { return "invert" }
837
+ get value() { return this._value }
838
+ toString() { return `calc(1 / ${this._formatOperand(this._value, 'product')})` }
839
+ type() {
840
+ const valType = this._value.type()
841
+ const invType = {}
842
+ for (const key in valType) {
843
+ invType[key] = -valType[key]
844
+ } return (this._value instanceof CSSVariableReferenceValue) ? {} : invType
845
+ } // Handle inner var()
846
+ }
847
+
848
+ class CSSMathMin extends CSSMathValue {
849
+ constructor(...operands) {
850
+ super()
851
+ if (operands.length === 0) throw new TypeError("CSSMathMin needs >= 1 argument.")
852
+ this._operands = Object.freeze(operands.map((op, i) => {
853
+ if (op instanceof CSSNumericValue || op instanceof CSSVariableReferenceValue) return op
854
+ throw new TypeError(`CSSMathMin operand ${i + 1} invalid type ${op?.constructor?.name}`)
855
+ }))
856
+ }
857
+ get operator() { return "min" }
858
+ toString() { return `min(${this._operands.map(op => op.toString()).join(", ")})` }
859
+ type() { return this._calculateMinMaxType() }
860
+ _calculateMinMaxType() {
861
+ let commonType = null
862
+ let hasVar = false
863
+ let hasUnknown = false
864
+ for (const op of this._operands) {
865
+ let opType
866
+ if (op instanceof CSSVariableReferenceValue) {
867
+ hasVar = true
868
+ opType = {}
869
+ }
870
+ else if (op instanceof CSSNumericValue) {
871
+ opType = op.type()
872
+ }
873
+ else {
874
+ hasUnknown = true
875
+ continue
876
+ }
877
+ const isOpVar = (op instanceof CSSVariableReferenceValue)
878
+ if (Object.keys(opType).length === 0 && !isOpVar && !(op instanceof CSSNumericValue && op._unitType === NUMBER_UNIT_TYPE)) {
879
+ hasUnknown = true
880
+ continue
881
+ }
882
+ if (!commonType && !hasUnknown) {
883
+ commonType = { ...opType }
884
+ }
885
+ else if (!hasUnknown && !isOpVar) { /* Check compatibility (length/percent mix allowed) */
886
+ const commonCat = Object.keys(commonType).find(k => commonType[k] !== 0 && k !== 'percent')
887
+ const opCat = Object.keys(opType).find(k => opType[k] !== 0 && k !== 'percent')
888
+ let compat = false
889
+ if (!commonCat && !opCat) compat = true
890
+ // num/perc + num/perc
891
+ else if (commonCat === 'length' && !opCat) compat = true
892
+ // len + num/perc
893
+ else if (!commonCat && opCat === 'length') compat = true
894
+ // num/perc + len
895
+ else if (commonCat && commonCat === opCat) compat = true
896
+ // unit + same unit
897
+ if (compat) {
898
+ if (opType.percent) commonType.percent = 1
899
+ /* Update mix */
900
+ }
901
+ else {
902
+ console.error(`${this.constructor.name} incompatible types: {${Object.keys(commonType).filter(k => commonType[k] !== 0)}} vs {${Object.keys(opType).filter(k => opType[k] !== 0)}}`)
903
+ return {}
904
+ }
905
+ }
906
+ }
907
+ if (hasUnknown && !commonType) return {}
908
+ if (hasVar) console.warn(`${this.constructor.name}.type(): Includes var(), result indeterminate.`)
909
+ return commonType || {}
910
+ }
911
+ }
912
+
913
+ class CSSMathMax extends CSSMathValue {
914
+ constructor(...operands) {
915
+ super()
916
+ if (operands.length === 0) throw new TypeError("CSSMathMax needs >= 1 argument.")
917
+ this._operands = Object.freeze(operands.map((op, i) => {
918
+ if (op instanceof CSSNumericValue || op instanceof CSSVariableReferenceValue) return op
919
+ throw new TypeError(`CSSMathMax operand ${i + 1} invalid type ${op?.constructor?.name}`)
920
+ }))
921
+ }
922
+ get operator() { return "max" }
923
+ toString() { return `max(${this._operands.map(op => op.toString()).join(", ")})` }
924
+ type() { return CSSMathMin.prototype._calculateMinMaxType.call(this) }
925
+ }
926
+
927
+ // --- CSSImageValue (Stub) ---
928
+ class CSSImageValue extends CSSStyleValue {
929
+ constructor(t) {
930
+ super()
931
+ this._cssText = t
932
+ }
933
+ toString() { return this._cssText }
934
+ }
935
+ // --- CSSPositionValue (Stub) ---
936
+ class CSSPositionValue extends CSSStyleValue {
937
+ constructor(x, y) {
938
+ super()
939
+ this.x = x
940
+ this.y = y
941
+ /* simplified */
942
+ }
943
+ toString() { return `${this.x} ${this.y}` }
944
+ }
945
+ // --- CSSTransformValue Stubs ---
946
+ class CSSTransformComponent extends CSSStyleValue {
947
+ constructor() {
948
+ super()
949
+ if (this.constructor === CSSTransformComponent) throw new TypeError("Abstract")
950
+ this.is2D = true
951
+ }
952
+ }
953
+ class CSSTranslate extends CSSTransformComponent {
954
+ constructor(x, y, z = null) {
955
+ super()
956
+ this.x = x
957
+ this.y = y
958
+ this.z = z
959
+ this.is2D = (z === null)
960
+ }
961
+ toString() { return this.is2D ? `translate(${this.x}, ${this.y})` : `translate3d(${this.x}, ${this.y}, ${this.z})` }
962
+ }
963
+ class CSSRotate extends CSSTransformComponent {
964
+ constructor(a, y = null, z = null, angle = null) {
965
+ super()
966
+ this.is2D = (y === null && z === null && angle === null)
967
+
968
+ if (this.is2D) {
969
+ if (!(angleOrX instanceof CSSNumericValue) || angleOrX.type().angle !== 1) {
970
+ // Allow unitless 0? Spec says <angle>
971
+ if (!((angleOrX instanceof CSSUnitValue && angleOrX.value === 0) || (angleOrX instanceof CSSNumericValue && angleOrX._unitType === NUMBER_UNIT_TYPE && angleOrX._value === 0))) {
972
+ throw new TypeError(`CSSRotate angle must be CSSNumericValue of type angle, got ${angleOrX?.toString()}`)
973
+ }
974
+ }
975
+ this.angle = angleOrX
976
+ this.x = this.y = this.z = CSS.number(0); // Store axis for consistency? No, keep null per spec.
977
+ } else {
978
+ // rotate3d(x, y, z, angle)
979
+ const parseNum = (n, name) => {
980
+ if (typeof n !== 'number') throw new TypeError(`CSSRotate ${name} must be a number.`)
981
+ return CSS.number(n); // Store as CSSNumericValue internally
982
+ }
983
+ this.x = parseNum(angleOrX, 'x')
984
+ this.y = parseNum(y, 'y')
985
+ this.z = parseNum(z, 'z')
986
+
987
+ if (!(angle instanceof CSSNumericValue) || angle.type().angle !== 1) {
988
+ if (!((angle instanceof CSSUnitValue && angle.value === 0) || (angle instanceof CSSNumericValue && angle._unitType === NUMBER_UNIT_TYPE && angle._value === 0))) {
989
+ throw new TypeError(`CSSRotate 3D angle must be CSSNumericValue of type angle, got ${angle?.toString()}`)
990
+ }
991
+ }
992
+ this.angle = angle
993
+ }
994
+ }
995
+ toString() { return this.is2D ? `rotate(${this.angle})` : `rotate3d(${this.x._value}, ${this.y._value}, ${this.z._value}, ${this.angle.toString()})` }
996
+ }
997
+ class CSSScale extends CSSTransformComponent {
998
+ constructor(x, y, z = null) {
999
+ super()
1000
+ const parseArg = (arg, name) => {
1001
+ let numVal
1002
+ if (arg instanceof CSSNumericValue) {
1003
+ if (Object.values(arg.type()).some(v => v !== 0)) { // Must be unitless
1004
+ throw new TypeError(`CSSScale ${name} must be a unitless number, got ${arg.toString()}`)
1005
+ }
1006
+ numVal = arg; // Already correct type
1007
+ } else if (typeof arg === 'number' && isFinite(arg)) {
1008
+ numVal = CSS.number(arg); // Convert number to CSSNumericValue
1009
+ } else {
1010
+ throw new TypeError(`CSSScale ${name} must be a finite number or unitless CSSNumericValue.`)
1011
+ }
1012
+ return numVal
1013
+ }
1014
+
1015
+ this.x = parseArg(x, 'x')
1016
+ // If y is explicitly undefined or null, it defaults to x. If provided, parse it.
1017
+ this.y = (y === undefined || y === null) ? this.x : parseArg(y, 'y')
1018
+
1019
+ // If z is explicitly null, it's a 2D scale. If provided, parse it.
1020
+ if (z !== null) {
1021
+ this.z = parseArg(z, 'z')
1022
+ this.is2D = false
1023
+ } else {
1024
+ this.z = CSS.number(1); // Default z to 1 for internal matrix math, but mark as 2D conceptually
1025
+ this.is2D = true
1026
+ }
1027
+ }
1028
+ toString() { return this.is2D ? (this.x == this.y ? `scale(${this.x})` : `scale(${this.x}, ${this.y})`) : `scale3d(${this.x}, ${this.y}, ${this.z})` }
1029
+ }
1030
+ class CSSTransformValue extends CSSStyleValue {
1031
+ constructor(t = []) {
1032
+ super()
1033
+ if (!Array.isArray(t) || !t.every(i => i instanceof CSSTransformComponent)) throw new TypeError("Invalid args")
1034
+ this.transforms = Object.freeze([...t])
1035
+ }
1036
+ get length() { return this.transforms.length }
1037
+ item(i) { return this.transforms[i] }
1038
+ get is2D() { return this.transforms.every(i => i.is2D) }
1039
+ toString() { return this.transforms.map(i => i.toString()).join(" ") }
1040
+ [Symbol.iterator]() { return this.transforms[Symbol.iterator]() }
1041
+ entries() { return this.transforms.entries(); }
1042
+ keys() { return this.transforms.keys(); }
1043
+ values() { return this.transforms.values(); }
1044
+ forEach(callback, thisArg) { this.transforms.forEach(callback, thisArg); }
1045
+ }
1046
+
1047
+ // --- CSSStyleValue Static Parsers (Implementation) ---
1048
+ CSSStyleValue.parse = function (property, cssText) {
1049
+ cssText = cssText.trim()
1050
+ if (cssText === '') throw new TypeError(`Cannot parse empty string for property "${property}"`)
1051
+
1052
+ // 1. Top-level var()
1053
+ const varMatch = cssText.match(/^var\(\s*(--[a-zA-Z0-9_-]+)\s*(?:,\s*(.+)\s*)?\)$/i)
1054
+ if (varMatch) return new CSSVariableReferenceValue(varMatch[1], varMatch[2] ? new CSSUnparsedValue([varMatch[2].trim()]) : null)
1055
+
1056
+ // 2. Top-level math functions
1057
+ const mathMatch = cssText.match(/^([a-z-]+)\((.+)\)$/i)
1058
+ if (mathMatch) {
1059
+ const funcName = mathMatch[1].toLowerCase()
1060
+ const content = mathMatch[2]
1061
+ switch (funcName) {
1062
+ case 'calc':
1063
+ try {
1064
+ return parseCssMathExpression(content)
1065
+ } catch (e) {
1066
+ console.warn(`Parse failed for calc(${content}): ${e.message}`)
1067
+ return new CSSUnparsedValue([cssText])
1068
+ }
1069
+ case 'min':
1070
+ case 'max':
1071
+ try {
1072
+ const args = parseFunctionArguments(content, funcName)
1073
+ args.forEach(a => {
1074
+ if (!(a instanceof CSSNumericValue || a instanceof CSSVariableReferenceValue)) throw new Error('Invalid arg')
1075
+ })
1076
+ if (args.length === 0) throw new Error('No args')
1077
+ return funcName === 'min' ? new CSSMathMin(...args) : new CSSMathMax(...args)
1078
+ }
1079
+ catch (e) {
1080
+ console.warn(`Parse failed for ${funcName}(${content}): ${e.message}`)
1081
+ return new CSSUnparsedValue([cssText])
1082
+ }
1083
+ case 'clamp': console.warn('clamp() not parsed')
1084
+ return new CSSUnparsedValue([cssText])
1085
+ }
1086
+ }
1087
+
1088
+ // 3. url() -> CSSImageValue stub
1089
+ if (cssText.match(/^url\(/i)) return new CSSImageValue(cssText)
1090
+
1091
+ // 4. Simple Number/Unit/Percent
1092
+ const simpleNum = parseSimpleCssNumericValue(cssText)
1093
+ if (simpleNum) return simpleNum
1094
+
1095
+ // 5. Keyword
1096
+ if (/^(-?[_a-zA-Z][_a-zA-Z0-9-]*)$/.test(cssText)) {
1097
+ if (!['calc', 'min', 'max', 'clamp', 'var'].includes(cssText.toLowerCase().split('(')[0])) {
1098
+ // Avoid treating numbers as keywords unless specific ones
1099
+ if (!/^-?\d/.test(cssText) || ['inherit', 'initial', 'unset', 'revert', 'auto', 'none'].includes(cssText.toLowerCase())) {
1100
+ return new CSSKeywordValue(cssText)
1101
+ }
1102
+ }
1103
+ }
1104
+
1105
+ // 6. String Literal
1106
+ if (cssText.match(/^(['"]).*\1$/)) return new CSSUnparsedValue([cssText])
1107
+
1108
+ // 7. Color Stubs (rgb, #)
1109
+ if (cssText.match(/^(rgb|hsl)a?\(|^#/) || ['transparent', 'currentcolor'].includes(cssText.toLowerCase())) {
1110
+ if (cssText.startsWith('rgb') || cssText.startsWith('hsl') || cssText.startsWith('#')) {
1111
+ console.warn(`Color value "${cssText}" returned as Unparsed.`)
1112
+ return new CSSUnparsedValue([cssText])
1113
+ }
1114
+ // Named colors / keywords handled above
1115
+ }
1116
+
1117
+ // 8. Transform Function Stubs
1118
+ if (cssText.match(/^(translate|rotate|scale|skew|matrix|perspective)[XYZ3d]?\(/i)) {
1119
+ console.warn(`Transform function "${cssText}" returned as Unparsed.`)
1120
+ return new CSSUnparsedValue([cssText])
1121
+ }
1122
+
1123
+ // Fallback
1124
+ console.warn(`CSSStyleValue.parse: Could not parse "${cssText}". Returning as CSSUnparsedValue.`)
1125
+ return new CSSUnparsedValue([cssText])
1126
+ }
1127
+
1128
+ CSSStyleValue.parseAll = function (property, cssText) {
1129
+ /* ... comma splitting logic (mostly unchanged) ... */
1130
+ cssText = cssText.trim()
1131
+ if (cssText === '') return []
1132
+ const values = []
1133
+ let current = ''
1134
+ let depth = 0
1135
+ let quote = null
1136
+ for (let i = 0; i < cssText.length; ++i) {
1137
+ const c = cssText[i]
1138
+ if (quote) {
1139
+ current += c
1140
+ if (c === quote && cssText[i - 1] !== '\\') quote = null
1141
+ }
1142
+ else if (c === '"' || c === "'") {
1143
+ current += c
1144
+ quote = c
1145
+ }
1146
+ else if (c === '(') {
1147
+ current += c
1148
+ depth++
1149
+ }
1150
+ else if (c === ')') {
1151
+ current += c
1152
+ depth = Math.max(0, depth - 1)
1153
+ }
1154
+ else if (c === ',' && depth === 0) {
1155
+ values.push(current.trim())
1156
+ current = ''
1157
+ }
1158
+ else {
1159
+ current += c
1160
+ }
1161
+ }
1162
+ if (current.trim() || values.length > 0) values.push(current.trim())
1163
+ return values.filter(v => v !== '').map(v => {
1164
+ try {
1165
+ return CSSStyleValue.parse(property, v)
1166
+ } catch (e) {
1167
+ console.warn(`parseAll segment failed for "${v}": ${e.message}`)
1168
+ return new CSSUnparsedValue([v])
1169
+ }
1170
+ })
1171
+ }
1172
+
1173
+ class StylePropertyMap {
1174
+ constructor(element) {
1175
+ this._element = element
1176
+ }
1177
+ get size() { return this._element.style.length }
1178
+ get(prop) {
1179
+ const kprop = this._kebab(prop)
1180
+ const v = this._element.style.getPropertyValue(kprop)
1181
+ if (!v) return null
1182
+ try {
1183
+ return CSSStyleValue.parse(kprop, v)
1184
+ } catch (e) {
1185
+ console.warn(`get failed for ${kprop}: ${e.message}`)
1186
+ return new CSSUnparsedValue([v])
1187
+ }
1188
+ }
1189
+ getAll(prop) {
1190
+ const kprop = this._kebab(prop)
1191
+ const v = this._element.style.getPropertyValue(kprop)
1192
+ if (!v) return []
1193
+ try {
1194
+ return CSSStyleValue.parseAll(kprop, v)
1195
+ } catch (e) {
1196
+ console.warn(`getAll failed for ${kprop}: ${e.message}`)
1197
+ return [new CSSUnparsedValue([v])]
1198
+ }
1199
+ }
1200
+ set(prop, ...vals) {
1201
+ const kprop = this._kebab(prop)
1202
+ if (vals.length === 0) throw new TypeError('Set requires values.')
1203
+ const txt = vals.map(v => v instanceof CSSStyleValue ? v.toString() : String(v)).join(' ').trim()
1204
+ try {
1205
+ this._element.style.setProperty(kprop, txt)
1206
+ } catch (e) {
1207
+ console.error(`Set failed for ${kprop}=${txt}: ${e}`)
1208
+ throw e
1209
+ }
1210
+ }
1211
+ append(prop, ...vals) {
1212
+ const kprop = this._kebab(prop)
1213
+ if (vals.length === 0) throw new TypeError('Append requires values.')
1214
+ const newTxt = vals.map(v => v instanceof CSSStyleValue ? v.toString() : String(v)).join(' ').trim()
1215
+ if (!newTxt) return
1216
+ const oldTxt = this._element.style.getPropertyValue(kprop)
1217
+ const listProps = ['font-family', 'text-shadow', 'box-shadow', 'filter', 'transition', 'animation']
1218
+ const isList = listProps.some(p => kprop.includes(p))
1219
+ let finalTxt
1220
+ if (oldTxt && isList) finalTxt = `${oldTxt}, ${newTxt}`
1221
+ else if (oldTxt) finalTxt = `${oldTxt} ${newTxt}`
1222
+ else finalTxt = newTxt
1223
+ try {
1224
+ this._element.style.setProperty(kprop, finalTxt)
1225
+ } catch (e) {
1226
+ console.error(`Append failed for ${kprop}=${finalTxt}: ${e}`)
1227
+ throw e
1228
+ }
1229
+ }
1230
+ delete(prop) {
1231
+ const kprop = this._kebab(prop)
1232
+ try {
1233
+ this._element.style.removeProperty(kprop)
1234
+ } catch (e) {
1235
+ console.error(`Delete failed for ${kprop}: ${e}`)
1236
+ throw e
1237
+ }
1238
+ }
1239
+ clear() { this._element.style.cssText = '' }
1240
+ has(prop) { return this._element.style.getPropertyValue(this._kebab(prop)) !== '' }
1241
+ _kebab(s) { return typeof s === 'string' ? s.replace(/([A-Z])/g, m => `-${m.toLowerCase()}`) : s }
1242
+ // Iterators... (rely on style object, iterates longhands)
1243
+ forEach(cb, thisArg) {
1244
+ const s = this._element.style
1245
+ for (let i = 0; i < s.length; ++i) {
1246
+ const p = s[i]
1247
+ const v = this.get(p)
1248
+ if (v !== null) cb.call(thisArg, v, p, this)
1249
+ }
1250
+ }
1251
+ [Symbol.iterator]() {
1252
+ const s = this._element.style
1253
+ let i = 0
1254
+ const map = this
1255
+ return {
1256
+ next() {
1257
+ if (i < s.length) {
1258
+ const p = s[i++]
1259
+ const v = map.get(p)
1260
+ return v !== null ? { value: [p, v], done: false } : this.next()
1261
+ }
1262
+ return { value: undefined, done: true }
1263
+ },
1264
+ [Symbol.iterator]() { return this }
1265
+ }
1266
+ }
1267
+ entries() { return this[Symbol.iterator]() }
1268
+ keys() {
1269
+ const s = this._element.style
1270
+ let i = 0
1271
+ return {
1272
+ next() {
1273
+ return i < s.length ? { value: s[i++], done: false } : { value: undefined, done: true }
1274
+ },
1275
+ [Symbol.iterator]() { return this }
1276
+ }
1277
+ }
1278
+ values() {
1279
+ const s = this._element.style
1280
+ let i = 0
1281
+ const map = this
1282
+ return {
1283
+ next() {
1284
+ if (i < s.length) {
1285
+ const p = s[i++]
1286
+ const v = map.get(p)
1287
+ return v !== null ? { value: v, done: false } : this.next()
1288
+ }
1289
+ return { value: undefined, done: true }
1290
+ },
1291
+ [Symbol.iterator]() { return this }
1292
+ }
1293
+ }
1294
+ }
1295
+
1296
+
1297
+
1298
+ // --- Global Scope Injection ---
1299
+ if (typeof window !== 'undefined') {
1300
+ const target = window
1301
+ const defineProp = (obj, name, value) => {
1302
+ if (!obj.hasOwnProperty(name))
1303
+ Object.defineProperty(obj, name, { value, writable: true, enumerable: false, configurable: true })
1304
+ }
1305
+
1306
+ // Core Classes
1307
+ defineProp(target, 'CSSStyleValue', CSSStyleValue)
1308
+ defineProp(target, 'CSSNumericValue', CSSNumericValue)
1309
+ defineProp(target, 'CSSUnitValue', CSSUnitValue)
1310
+ defineProp(target, 'CSSKeywordValue', CSSKeywordValue)
1311
+ defineProp(target, 'CSSUnparsedValue', CSSUnparsedValue)
1312
+ defineProp(target, 'CSSVariableReferenceValue', CSSVariableReferenceValue)
1313
+ // Math Classes
1314
+ defineProp(target, 'CSSMathValue', CSSMathValue)
1315
+ defineProp(target, 'CSSMathSum', CSSMathSum)
1316
+ defineProp(target, 'CSSMathProduct', CSSMathProduct)
1317
+ defineProp(target, 'CSSMathNegate', CSSMathNegate)
1318
+ defineProp(target, 'CSSMathInvert', CSSMathInvert)
1319
+ defineProp(target, 'CSSMathMin', CSSMathMin)
1320
+ defineProp(target, 'CSSMathMax', CSSMathMax)
1321
+ // Other Stubs
1322
+ defineProp(target, 'CSSImageValue', CSSImageValue)
1323
+ defineProp(target, 'CSSPositionValue', CSSPositionValue)
1324
+ defineProp(target, 'CSSTransformValue', CSSTransformValue)
1325
+ defineProp(target, 'CSSTranslate', CSSTranslate)
1326
+ defineProp(target, 'CSSRotate', CSSRotate)
1327
+ defineProp(target, 'CSSScale', CSSScale)
1328
+ defineProp(target, 'CSSTransformComponent', CSSTransformComponent)
1329
+
1330
+ // attributeStyleMap
1331
+ if (typeof HTMLElement !== 'undefined' && !HTMLElement.prototype.hasOwnProperty('attributeStyleMap')) {
1332
+ const cache = new WeakMap()
1333
+ Object.defineProperty(HTMLElement.prototype, 'attributeStyleMap', {
1334
+ configurable: true, enumerable: true, get() {
1335
+ if (!cache.has(this)) cache.set(this, new StylePropertyMap(this))
1336
+ return cache.get(this)
1337
+ }, set() {
1338
+ throw new Error("Cannot set read-only property 'attributeStyleMap'.")
1339
+ }
1340
+ })
1341
+ console.log('Polyfilled HTMLElement.prototype.attributeStyleMap')
1342
+ }
1343
+
1344
+ // CSS.* factories
1345
+ if (typeof target.CSS === 'undefined') target.CSS = {}
1346
+ const defineCssFactory = (name, func) => {
1347
+ if (typeof target.CSS[name] !== 'function') Object.defineProperty(target.CSS, name, { value: func, writable: true, enumerable: true, configurable: true })
1348
+ }
1349
+ defineCssFactory('number', v => {
1350
+ if (typeof v !== 'number' || !isFinite(v)) throw new TypeError('CSS.number needs finite number')
1351
+ return new CSSNumericValue(v, NUMBER_UNIT_TYPE)
1352
+ })
1353
+ ALL_UNITS.forEach(unit => {
1354
+ let name = unit.toLowerCase()
1355
+ if (name === '%') name = 'percent'
1356
+ if (!/^[a-z]/.test(name)) return
1357
+ defineCssFactory(name, v => {
1358
+ if (typeof v !== 'number' || !isFinite(v)) throw new TypeError(`CSS.${name} needs finite number`)
1359
+ return new CSSUnitValue(v, unit)
1360
+ })
1361
+ })
1362
+ console.log('Polyfilled CSS.* factory functions')
1363
+
1364
+ console.log('CSS Typed OM Polyfill: Finished initialization.')
1365
+ } else {
1366
+ console.warn('CSS Typed OM Polyfill: Not running in a browser environment.')
1367
+ }
1368
+
1369
+ })()