css-typed-om-polyfill 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +119 -185
- package/README.zh.md +115 -182
- package/cssom.js +1732 -0
- package/package.json +3 -3
- package/css-typed-om-polyfill.js +0 -1369
package/css-typed-om-polyfill.js
DELETED
|
@@ -1,1369 +0,0 @@
|
|
|
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
|
-
})()
|