cssstyle 5.3.6 → 6.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.
Files changed (93) hide show
  1. package/lib/CSSStyleDeclaration.js +360 -395
  2. package/lib/generated/.gitkeep +0 -0
  3. package/lib/generated/propertyDefinitions.js +3685 -1498
  4. package/lib/generated/propertyDescriptors.js +1705 -0
  5. package/lib/index.js +9 -0
  6. package/lib/normalize.js +176 -309
  7. package/lib/parsers.js +155 -191
  8. package/lib/properties/background.js +201 -202
  9. package/lib/properties/backgroundAttachment.js +35 -33
  10. package/lib/properties/backgroundClip.js +35 -33
  11. package/lib/properties/backgroundColor.js +27 -25
  12. package/lib/properties/backgroundImage.js +36 -34
  13. package/lib/properties/backgroundOrigin.js +35 -33
  14. package/lib/properties/backgroundPosition.js +57 -57
  15. package/lib/properties/backgroundRepeat.js +40 -37
  16. package/lib/properties/backgroundSize.js +38 -34
  17. package/lib/properties/border.js +41 -34
  18. package/lib/properties/{webkitBorderEndColor.js → borderBlockEndColor.js} +27 -23
  19. package/lib/properties/{webkitBorderAfterColor.js → borderBlockStartColor.js} +27 -23
  20. package/lib/properties/borderBottom.js +40 -36
  21. package/lib/properties/borderBottomColor.js +26 -22
  22. package/lib/properties/borderBottomStyle.js +26 -22
  23. package/lib/properties/borderBottomWidth.js +29 -25
  24. package/lib/properties/borderCollapse.js +26 -22
  25. package/lib/properties/borderColor.js +36 -33
  26. package/lib/properties/{webkitBorderStartColor.js → borderInlineEndColor.js} +27 -23
  27. package/lib/properties/borderInlineStartColor.js +49 -0
  28. package/lib/properties/borderLeft.js +40 -36
  29. package/lib/properties/borderLeftColor.js +26 -22
  30. package/lib/properties/borderLeftStyle.js +26 -22
  31. package/lib/properties/borderLeftWidth.js +29 -25
  32. package/lib/properties/borderRight.js +40 -36
  33. package/lib/properties/borderRightColor.js +26 -22
  34. package/lib/properties/borderRightStyle.js +26 -22
  35. package/lib/properties/borderRightWidth.js +29 -25
  36. package/lib/properties/borderSpacing.js +33 -29
  37. package/lib/properties/borderStyle.js +36 -33
  38. package/lib/properties/borderTop.js +40 -36
  39. package/lib/properties/borderTopColor.js +26 -22
  40. package/lib/properties/borderTopStyle.js +26 -22
  41. package/lib/properties/borderTopWidth.js +29 -25
  42. package/lib/properties/borderWidth.js +36 -33
  43. package/lib/properties/bottom.js +28 -24
  44. package/lib/properties/clear.js +26 -22
  45. package/lib/properties/clip.js +37 -31
  46. package/lib/properties/color.js +26 -22
  47. package/lib/properties/display.js +36 -30
  48. package/lib/properties/flex.js +53 -45
  49. package/lib/properties/flexBasis.js +29 -27
  50. package/lib/properties/flexGrow.js +29 -27
  51. package/lib/properties/flexShrink.js +29 -27
  52. package/lib/properties/float.js +26 -22
  53. package/lib/properties/floodColor.js +26 -22
  54. package/lib/properties/font.js +89 -118
  55. package/lib/properties/fontFamily.js +38 -33
  56. package/lib/properties/fontSize.js +30 -28
  57. package/lib/properties/fontStyle.js +38 -34
  58. package/lib/properties/fontVariant.js +35 -33
  59. package/lib/properties/fontWeight.js +34 -32
  60. package/lib/properties/height.js +29 -25
  61. package/lib/properties/left.js +28 -24
  62. package/lib/properties/lightingColor.js +26 -22
  63. package/lib/properties/lineHeight.js +29 -27
  64. package/lib/properties/margin.js +40 -34
  65. package/lib/properties/marginBottom.js +31 -28
  66. package/lib/properties/marginLeft.js +31 -28
  67. package/lib/properties/marginRight.js +31 -28
  68. package/lib/properties/marginTop.js +31 -28
  69. package/lib/properties/opacity.js +28 -24
  70. package/lib/properties/outlineColor.js +26 -22
  71. package/lib/properties/padding.js +40 -34
  72. package/lib/properties/paddingBottom.js +32 -29
  73. package/lib/properties/paddingLeft.js +32 -29
  74. package/lib/properties/paddingRight.js +32 -29
  75. package/lib/properties/paddingTop.js +32 -29
  76. package/lib/properties/right.js +28 -24
  77. package/lib/properties/stopColor.js +26 -22
  78. package/lib/properties/{webkitBorderBeforeColor.js → textEmphasisColor.js} +27 -23
  79. package/lib/properties/top.js +28 -25
  80. package/lib/properties/webkitTextFillColor.js +26 -22
  81. package/lib/properties/webkitTextStrokeColor.js +26 -22
  82. package/lib/properties/width.js +29 -25
  83. package/lib/utils/propertyDescriptors.js +129 -42
  84. package/lib/utils/strings.js +11 -156
  85. package/package.json +11 -21
  86. package/lib/generated/allProperties.js +0 -653
  87. package/lib/generated/implementedProperties.js +0 -1466
  88. package/lib/generated/properties.js +0 -6638
  89. package/lib/properties/webkitColumnRuleColor.js +0 -45
  90. package/lib/properties/webkitTapHighlightColor.js +0 -45
  91. package/lib/properties/webkitTextEmphasisColor.js +0 -45
  92. package/lib/utils/allExtraProperties.js +0 -155
  93. package/lib/utils/camelize.js +0 -37
@@ -4,9 +4,7 @@
4
4
  */
5
5
  "use strict";
6
6
 
7
- const allProperties = require("./generated/allProperties");
8
- const implementedProperties = require("./generated/implementedProperties");
9
- const generatedProperties = require("./generated/properties");
7
+ const propertyDescriptors = require("./generated/propertyDescriptors");
10
8
  const {
11
9
  borderProperties,
12
10
  getPositionValue,
@@ -15,16 +13,7 @@ const {
15
13
  prepareProperties,
16
14
  shorthandProperties
17
15
  } = require("./normalize");
18
- const {
19
- hasVarFunc,
20
- isGlobalKeyword,
21
- parseCSS,
22
- parsePropertyValue,
23
- prepareValue
24
- } = require("./parsers");
25
- const allExtraProperties = require("./utils/allExtraProperties");
26
- const { dashedToCamelCase } = require("./utils/camelize");
27
- const { getPropertyDescriptor } = require("./utils/propertyDescriptors");
16
+ const { hasVarFunc, isGlobalKeyword, parseCSS, parsePropertyValue, prepareValue } = require("./parsers");
28
17
  const { asciiLowercase } = require("./utils/strings");
29
18
 
30
19
  /**
@@ -32,88 +21,38 @@ const { asciiLowercase } = require("./utils/strings");
32
21
  */
33
22
  class CSSStyleDeclaration {
34
23
  /**
35
- * @param {Function} onChangeCallback
36
- * @param {object} [opt]
37
- * @param {object} [opt.context] - Window, Element or CSSRule.
24
+ * Creates a new CSSStyleDeclaration instance.
25
+ *
26
+ * @param {Function} [onChangeCallback] - Callback triggered when style changes.
27
+ * @param {object} [opt] - Options.
28
+ * @param {object} [opt.context] - The context object (Window, Element, or CSSRule).
38
29
  */
39
- constructor(onChangeCallback, opt = {}) {
40
- // Make constructor and internals non-enumerable.
41
- Object.defineProperties(this, {
42
- constructor: {
43
- enumerable: false,
44
- writable: true
45
- },
46
-
47
- // Window
48
- _global: {
49
- value: globalThis,
50
- enumerable: false,
51
- writable: true
52
- },
53
-
54
- // Element
55
- _ownerNode: {
56
- value: null,
57
- enumerable: false,
58
- writable: true
59
- },
60
-
61
- // CSSRule
62
- _parentNode: {
63
- value: null,
64
- enumerable: false,
65
- writable: true
66
- },
67
-
68
- _onChange: {
69
- value: null,
70
- enumerable: false,
71
- writable: true
72
- },
73
-
74
- _values: {
75
- value: new Map(),
76
- enumerable: false,
77
- writable: true
78
- },
30
+ constructor(onChangeCallback, { context } = {}) {
31
+ // Internals for jsdom
32
+ this._global = globalThis;
33
+ this._onChange = onChangeCallback;
34
+
35
+ // Internals for CSS declaration block
36
+ // @see https://drafts.csswg.org/cssom/#css-declaration-blocks
37
+ this._computed = false;
38
+ this._ownerNode = null;
39
+ this._parentRule = null;
40
+ this._readonly = false;
41
+ this._updating = false;
42
+
43
+ // Other internals
44
+ this._length = 0;
45
+ this._propertyIndices = new Map();
46
+ this._priorities = new Map();
47
+ this._values = new Map();
79
48
 
80
- _priorities: {
81
- value: new Map(),
82
- enumerable: false,
83
- writable: true
84
- },
85
-
86
- _length: {
87
- value: 0,
88
- enumerable: false,
89
- writable: true
90
- },
91
-
92
- _computed: {
93
- value: false,
94
- enumerable: false,
95
- writable: true
96
- },
97
-
98
- _readonly: {
99
- value: false,
100
- enumerable: false,
101
- writable: true
102
- },
103
-
104
- _setInProgress: {
105
- value: false,
106
- enumerable: false,
107
- writable: true
108
- }
109
- });
110
-
111
- const { context } = opt;
112
49
  if (context) {
113
50
  if (typeof context.getComputedStyle === "function") {
114
51
  this._global = context;
115
52
  this._computed = true;
116
- this._readonly = true;
53
+ // FIXME: The `_readonly` flag should initially be `false` to be editable,
54
+ // but should eventually be set to `true`.
55
+ // this._readonly = true;
117
56
  } else if (context.nodeType === 1 && Object.hasOwn(context, "style")) {
118
57
  this._global = context.ownerDocument.defaultView;
119
58
  this._ownerNode = context;
@@ -126,11 +65,13 @@ class CSSStyleDeclaration {
126
65
  }
127
66
  }
128
67
  }
129
- if (typeof onChangeCallback === "function") {
130
- this._onChange = onChangeCallback;
131
- }
132
68
  }
133
69
 
70
+ /**
71
+ * Returns the textual representation of the declaration block.
72
+ *
73
+ * @returns {string} The serialized CSS text.
74
+ */
134
75
  get cssText() {
135
76
  if (this._computed) {
136
77
  return "";
@@ -162,28 +103,27 @@ class CSSStyleDeclaration {
162
103
  return parts.join(" ");
163
104
  }
164
105
 
165
- set cssText(val) {
106
+ /**
107
+ * Sets the textual representation of the declaration block.
108
+ * This clears all existing properties and parses the new CSS text.
109
+ *
110
+ * @param {string} text - The new CSS text.
111
+ */
112
+ set cssText(text) {
166
113
  if (this._readonly) {
167
114
  const msg = "cssText can not be modified.";
168
115
  const name = "NoModificationAllowedError";
169
116
  throw new this._global.DOMException(msg, name);
170
117
  }
171
- Array.prototype.splice.call(this, 0, this._length);
118
+ this._clearIndexedProperties();
172
119
  this._values.clear();
173
120
  this._priorities.clear();
174
- if (this._parentRule || (this._ownerNode && this._setInProgress)) {
121
+ if (this._parentRule || (this._ownerNode && this._updating)) {
175
122
  return;
176
123
  }
177
- this._setInProgress = true;
178
124
  try {
179
- const valueObj = parseCSS(
180
- val,
181
- {
182
- context: "declarationList",
183
- parseValue: false
184
- },
185
- true
186
- );
125
+ this._updating = true;
126
+ const valueObj = parseCSS(text, { context: "declarationList", parseValue: false });
187
127
  if (valueObj?.children) {
188
128
  const properties = new Map();
189
129
  let shouldSkipNext = false;
@@ -217,9 +157,7 @@ class CSSStyleDeclaration {
217
157
  properties.set(property, { property, value, priority });
218
158
  }
219
159
  } else {
220
- const parsedValue = parsePropertyValue(property, value, {
221
- globalObject: this._global
222
- });
160
+ const parsedValue = parsePropertyValue(property, value);
223
161
  if (parsedValue) {
224
162
  if (properties.has(property)) {
225
163
  const { priority: itemPriority } = properties.get(property);
@@ -235,9 +173,7 @@ class CSSStyleDeclaration {
235
173
  }
236
174
  }
237
175
  }
238
- const parsedProperties = prepareProperties(properties, {
239
- globalObject: this._global
240
- });
176
+ const parsedProperties = prepareProperties(properties);
241
177
  for (const [property, item] of parsedProperties) {
242
178
  const { priority, value } = item;
243
179
  this._priorities.set(property, priority);
@@ -246,49 +182,65 @@ class CSSStyleDeclaration {
246
182
  }
247
183
  } catch {
248
184
  return;
185
+ } finally {
186
+ this._updating = false;
249
187
  }
250
- this._setInProgress = false;
251
- if (typeof this._onChange === "function") {
188
+ if (this._onChange) {
252
189
  this._onChange(this.cssText);
253
190
  }
254
191
  }
255
192
 
193
+ /**
194
+ * Returns the number of properties in the declaration block.
195
+ *
196
+ * @returns {number} The property count.
197
+ */
256
198
  get length() {
257
199
  return this._length;
258
200
  }
259
201
 
260
- // This deletes indices if the new length is less then the current length.
261
- // If the new length is more, it does nothing, the new indices will be
262
- // undefined until set.
263
- set length(len) {
264
- for (let i = len; i < this._length; i++) {
265
- delete this[i];
266
- }
267
- this._length = len;
268
- }
269
-
270
- // Readonly
202
+ /**
203
+ * Returns the CSSRule that is the parent of this declaration block.
204
+ *
205
+ * @returns {object|null} The parent CSSRule or null.
206
+ */
271
207
  get parentRule() {
272
208
  return this._parentRule;
273
209
  }
274
210
 
211
+ /**
212
+ * Alias for the "float" property.
213
+ *
214
+ * @returns {string} The value of the "float" property.
215
+ */
275
216
  get cssFloat() {
276
217
  return this.getPropertyValue("float");
277
218
  }
278
219
 
220
+ /**
221
+ * Sets the "float" property.
222
+ *
223
+ * @param {string} value - The new value for "float".
224
+ */
279
225
  set cssFloat(value) {
280
226
  this._setProperty("float", value);
281
227
  }
282
228
 
283
229
  /**
284
- * @param {string} property
230
+ * Returns the priority of the specified property (e.g. "important").
231
+ *
232
+ * @param {string} property - The property name.
233
+ * @returns {string} The priority string, or empty string if not set.
285
234
  */
286
235
  getPropertyPriority(property) {
287
236
  return this._priorities.get(property) || "";
288
237
  }
289
238
 
290
239
  /**
291
- * @param {string} property
240
+ * Returns the value of the specified property.
241
+ *
242
+ * @param {string} property - The property name.
243
+ * @returns {string} The property value, or empty string if not set.
292
244
  */
293
245
  getPropertyValue(property) {
294
246
  if (this._values.has(property)) {
@@ -298,7 +250,10 @@ class CSSStyleDeclaration {
298
250
  }
299
251
 
300
252
  /**
301
- * @param {...number} args
253
+ * Returns the property name at the specified index.
254
+ *
255
+ * @param {...number} args - The index (only the first argument is used).
256
+ * @returns {string} The property name, or empty string if index is invalid.
302
257
  */
303
258
  item(...args) {
304
259
  if (!args.length) {
@@ -314,7 +269,10 @@ class CSSStyleDeclaration {
314
269
  }
315
270
 
316
271
  /**
317
- * @param {string} property
272
+ * Removes the specified property from the declaration block.
273
+ *
274
+ * @param {string} property - The property name to remove.
275
+ * @returns {string} The value of the removed property.
318
276
  */
319
277
  removeProperty(property) {
320
278
  if (this._readonly) {
@@ -328,10 +286,10 @@ class CSSStyleDeclaration {
328
286
  const prevValue = this._values.get(property);
329
287
  this._values.delete(property);
330
288
  this._priorities.delete(property);
331
- const index = Array.prototype.indexOf.call(this, property);
289
+ const index = this._getIndexOf(property);
332
290
  if (index >= 0) {
333
- Array.prototype.splice.call(this, index, 1);
334
- if (typeof this._onChange === "function") {
291
+ this._removeIndexedProperty(index);
292
+ if (this._onChange) {
335
293
  this._onChange(this.cssText);
336
294
  }
337
295
  }
@@ -339,30 +297,34 @@ class CSSStyleDeclaration {
339
297
  }
340
298
 
341
299
  /**
342
- * @param {string} prop
343
- * @param {string} val
344
- * @param {string} prior
300
+ * Sets a property value with an optional priority.
301
+ *
302
+ * @param {string} property - The property name.
303
+ * @param {string} value - The property value.
304
+ * @param {string} [priority=""] - The priority (e.g. "important").
345
305
  */
346
- setProperty(prop, val, prior) {
306
+ setProperty(property, value, priority = "") {
347
307
  if (this._readonly) {
348
- const msg = `Property ${prop} can not be modified.`;
308
+ const msg = `Property ${property} can not be modified.`;
349
309
  const name = "NoModificationAllowedError";
350
310
  throw new this._global.DOMException(msg, name);
351
311
  }
352
- const value = prepareValue(val, this._global);
312
+ value = prepareValue(value);
353
313
  if (value === "") {
354
- this[prop] = "";
355
- this.removeProperty(prop);
314
+ if (Object.hasOwn(propertyDescriptors, property)) {
315
+ // TODO: Refactor handlers to not require `.call()`.
316
+ propertyDescriptors[property].set.call(this, value);
317
+ }
318
+ this.removeProperty(property);
356
319
  return;
357
320
  }
358
- const priority = prior === "important" ? "important" : "";
359
- const isCustomProperty = prop.startsWith("--");
360
- if (isCustomProperty) {
361
- this._setProperty(prop, value, priority);
321
+ // Custom property
322
+ if (property.startsWith("--")) {
323
+ this._setProperty(property, value, priority);
362
324
  return;
363
325
  }
364
- const property = asciiLowercase(prop);
365
- if (!allProperties.has(property) && !allExtraProperties.has(property)) {
326
+ property = asciiLowercase(property);
327
+ if (!Object.hasOwn(propertyDescriptors, property)) {
366
328
  return;
367
329
  }
368
330
  if (priority) {
@@ -370,279 +332,282 @@ class CSSStyleDeclaration {
370
332
  } else {
371
333
  this._priorities.delete(property);
372
334
  }
373
- this[property] = value;
335
+ propertyDescriptors[property].set.call(this, value);
374
336
  }
375
- }
376
337
 
377
- // Internal methods
378
- Object.defineProperties(CSSStyleDeclaration.prototype, {
379
- _setProperty: {
380
- /**
381
- * @param {string} property
382
- * @param {string} val
383
- * @param {string} priority
384
- */
385
- value(property, val, priority) {
386
- if (typeof val !== "string") {
387
- return;
388
- }
389
- if (val === "") {
390
- this.removeProperty(property);
391
- return;
392
- }
393
- let originalText = "";
394
- if (typeof this._onChange === "function" && !this._setInProgress) {
395
- originalText = this.cssText;
396
- }
397
- if (this._values.has(property)) {
398
- const index = Array.prototype.indexOf.call(this, property);
399
- // The property already exists but is not indexed into `this` so add it.
400
- if (index < 0) {
401
- this[this._length] = property;
402
- this._length++;
403
- }
404
- } else {
405
- // New property.
406
- this[this._length] = property;
407
- this._length++;
408
- }
409
- if (priority === "important") {
410
- this._priorities.set(property, priority);
411
- } else {
412
- this._priorities.delete(property);
413
- }
414
- this._values.set(property, val);
415
- if (
416
- typeof this._onChange === "function" &&
417
- !this._setInProgress &&
418
- this.cssText !== originalText
419
- ) {
420
- this._onChange(this.cssText);
421
- }
422
- },
423
- enumerable: false
424
- },
338
+ /**
339
+ * Clears all indexed properties, properties indices and resets length to 0.
340
+ *
341
+ * @private
342
+ */
343
+ _clearIndexedProperties() {
344
+ this._propertyIndices.clear();
345
+ for (let i = 0; i < this._length; i++) {
346
+ delete this[i];
347
+ }
348
+ this._length = 0;
349
+ }
425
350
 
426
- _borderSetter: {
427
- /**
428
- * @param {string} prop
429
- * @param {object|Array|string} val
430
- * @param {string} prior
431
- */
432
- value(prop, val, prior) {
433
- const properties = new Map();
434
- if (prop === "border") {
435
- let priority = "";
436
- if (typeof prior === "string") {
437
- priority = prior;
438
- } else {
439
- priority = this._priorities.get(prop) ?? "";
440
- }
441
- properties.set(prop, { propery: prop, value: val, priority });
442
- } else {
443
- for (let i = 0; i < this._length; i++) {
444
- const property = this[i];
445
- if (borderProperties.has(property)) {
446
- const value = this.getPropertyValue(property);
447
- const longhandPriority = this._priorities.get(property) ?? "";
448
- let priority = longhandPriority;
449
- if (prop === property && typeof prior === "string") {
450
- priority = prior;
451
- }
452
- properties.set(property, { property, value, priority });
453
- }
454
- }
455
- }
456
- const parsedProperties = prepareBorderProperties(prop, val, prior, properties, {
457
- globalObject: this._global
458
- });
459
- for (const [property, item] of parsedProperties) {
460
- const { priority, value } = item;
461
- this._setProperty(property, value, priority);
462
- }
463
- },
464
- enumerable: false
465
- },
351
+ /**
352
+ * Removes an indexed property at the specified index, shifts others, and updates indices.
353
+ *
354
+ * @private
355
+ * @param {number} index - The index of the property to remove.
356
+ */
357
+ _removeIndexedProperty(index) {
358
+ this._propertyIndices.delete(this[index]);
359
+ for (let i = index; i < this._length - 1; i++) {
360
+ const property = this[i + 1];
361
+ this[i] = property;
362
+ this._propertyIndices.set(property, i);
363
+ }
364
+ delete this[this._length - 1];
365
+ this._length--;
366
+ }
466
367
 
467
- _flexBoxSetter: {
468
- /**
469
- * @param {string} prop
470
- * @param {string} val
471
- * @param {string} prior
472
- * @param {string} shorthandProperty
473
- */
474
- value(prop, val, prior, shorthandProperty) {
475
- if (!shorthandProperty || !shorthandProperties.has(shorthandProperty)) {
476
- return;
477
- }
478
- const shorthandPriority = this._priorities.get(shorthandProperty);
479
- this.removeProperty(shorthandProperty);
480
- let priority = "";
481
- if (typeof prior === "string") {
482
- priority = prior;
483
- } else {
484
- priority = this._priorities.get(prop) ?? "";
485
- }
486
- this.removeProperty(prop);
487
- if (shorthandPriority && priority) {
488
- this._setProperty(prop, val);
489
- } else {
490
- this._setProperty(prop, val, priority);
491
- }
492
- if (val && !hasVarFunc(val)) {
493
- const longhandValues = [];
494
- const shorthandItem = shorthandProperties.get(shorthandProperty);
495
- let hasGlobalKeyword = false;
496
- for (const [longhandProperty] of shorthandItem.shorthandFor) {
497
- if (longhandProperty === prop) {
498
- if (isGlobalKeyword(val)) {
499
- hasGlobalKeyword = true;
500
- }
501
- longhandValues.push(val);
502
- } else {
503
- const longhandValue = this.getPropertyValue(longhandProperty);
504
- const longhandPriority = this._priorities.get(longhandProperty) ?? "";
505
- if (!longhandValue || longhandPriority !== priority) {
506
- break;
507
- }
508
- if (isGlobalKeyword(longhandValue)) {
509
- hasGlobalKeyword = true;
510
- }
511
- longhandValues.push(longhandValue);
512
- }
513
- }
514
- if (longhandValues.length === shorthandItem.shorthandFor.size) {
515
- if (hasGlobalKeyword) {
516
- const [firstValue, ...restValues] = longhandValues;
517
- if (restValues.every((value) => value === firstValue)) {
518
- this._setProperty(shorthandProperty, firstValue, priority);
519
- }
520
- } else {
521
- const parsedValue = shorthandItem.parse(longhandValues.join(" "));
522
- const shorthandValue = Object.values(parsedValue).join(" ");
523
- this._setProperty(shorthandProperty, shorthandValue, priority);
524
- }
525
- }
526
- }
527
- },
528
- enumerable: false
529
- },
368
+ /**
369
+ * Returns the index of the specified property.
370
+ *
371
+ * @private
372
+ * @param {string} property - The property name to search for.
373
+ * @returns {number} The index of the property, or -1 if not found.
374
+ */
375
+ _getIndexOf(property) {
376
+ return this._propertyIndices.get(property) ?? -1;
377
+ }
530
378
 
531
- _positionShorthandSetter: {
532
- /**
533
- * @param {string} prop
534
- * @param {Array|string} val
535
- * @param {string} prior
536
- */
537
- value(prop, val, prior) {
538
- if (!shorthandProperties.has(prop)) {
539
- return;
540
- }
541
- const shorthandValues = [];
542
- if (Array.isArray(val)) {
543
- shorthandValues.push(...val);
544
- } else if (typeof val === "string") {
545
- shorthandValues.push(val);
546
- } else {
547
- return;
548
- }
549
- let priority = "";
550
- if (typeof prior === "string") {
551
- priority = prior;
552
- } else {
553
- priority = this._priorities.get(prop) ?? "";
554
- }
555
- const { position, shorthandFor } = shorthandProperties.get(prop);
556
- let hasPriority = false;
557
- for (const [longhandProperty, longhandItem] of shorthandFor) {
558
- const { position: longhandPosition } = longhandItem;
559
- const longhandValue = getPositionValue(shorthandValues, longhandPosition);
560
- if (priority) {
561
- this._setProperty(longhandProperty, longhandValue, priority);
562
- } else {
563
- const longhandPriority = this._priorities.get(longhandProperty) ?? "";
564
- if (longhandPriority) {
565
- hasPriority = true;
566
- } else {
567
- this._setProperty(longhandProperty, longhandValue, priority);
379
+ /**
380
+ * Sets a property and update indices.
381
+ *
382
+ * @private
383
+ * @param {string} property - The property name.
384
+ * @param {string} value - The property value.
385
+ * @param {string} priority - The priority.
386
+ */
387
+ _setProperty(property, value, priority) {
388
+ if (typeof value !== "string") {
389
+ return;
390
+ }
391
+ if (value === "") {
392
+ this.removeProperty(property);
393
+ return;
394
+ }
395
+ let originalText = "";
396
+ if (this._onChange && !this._updating) {
397
+ originalText = this.cssText;
398
+ }
399
+ if (!this._values.has(property)) {
400
+ // New property.
401
+ this[this._length] = property;
402
+ this._propertyIndices.set(property, this._length);
403
+ this._length++;
404
+ }
405
+ if (priority === "important") {
406
+ this._priorities.set(property, priority);
407
+ } else {
408
+ this._priorities.delete(property);
409
+ }
410
+ this._values.set(property, value);
411
+ if (this._onChange && !this._updating && this.cssText !== originalText) {
412
+ this._onChange(this.cssText);
413
+ }
414
+ }
415
+
416
+ /**
417
+ * Helper to handle border property expansion.
418
+ *
419
+ * @private
420
+ * @param {string} property - The property name (e.g. "border").
421
+ * @param {object|Array|string} value - The value to set.
422
+ * @param {string} priority - The priority.
423
+ */
424
+ _borderSetter(property, value, priority) {
425
+ const properties = new Map();
426
+ if (typeof priority !== "string") {
427
+ priority = this._priorities.get(property) ?? "";
428
+ }
429
+ if (property === "border") {
430
+ properties.set(property, { propery: property, value, priority });
431
+ } else {
432
+ for (let i = 0; i < this._length; i++) {
433
+ const itemProperty = this[i];
434
+ if (borderProperties.has(itemProperty)) {
435
+ const itemValue = this.getPropertyValue(itemProperty);
436
+ const longhandPriority = this._priorities.get(itemProperty) ?? "";
437
+ let itemPriority = longhandPriority;
438
+ if (itemProperty === property) {
439
+ itemPriority = priority;
568
440
  }
441
+ properties.set(itemProperty, {
442
+ property: itemProperty,
443
+ value: itemValue,
444
+ priority: itemPriority
445
+ });
569
446
  }
570
447
  }
571
- if (hasPriority) {
572
- this.removeProperty(prop);
573
- } else {
574
- const shorthandValue = getPositionValue(shorthandValues, position);
575
- this._setProperty(prop, shorthandValue, priority);
576
- }
577
- },
578
- enumerable: false
579
- },
448
+ }
449
+ const parsedProperties = prepareBorderProperties(property, value, priority, properties);
450
+ for (const [itemProperty, item] of parsedProperties) {
451
+ const { priority: itemPriority, value: itemValue } = item;
452
+ this._setProperty(itemProperty, itemValue, itemPriority);
453
+ }
454
+ }
580
455
 
581
- _positionLonghandSetter: {
582
- /**
583
- * @param {string} prop
584
- * @param {string} val
585
- * @param {string} prior
586
- * @param {string} shorthandProperty
587
- */
588
- value(prop, val, prior, shorthandProperty) {
589
- if (!shorthandProperty || !shorthandProperties.has(shorthandProperty)) {
590
- return;
591
- }
592
- const shorthandPriority = this._priorities.get(shorthandProperty);
593
- this.removeProperty(shorthandProperty);
594
- let priority = "";
595
- if (typeof prior === "string") {
596
- priority = prior;
597
- } else {
598
- priority = this._priorities.get(prop) ?? "";
599
- }
600
- this.removeProperty(prop);
601
- if (shorthandPriority && priority) {
602
- this._setProperty(prop, val);
603
- } else {
604
- this._setProperty(prop, val, priority);
605
- }
606
- if (val && !hasVarFunc(val)) {
607
- const longhandValues = [];
608
- const { shorthandFor, position: shorthandPosition } =
609
- shorthandProperties.get(shorthandProperty);
610
- for (const [longhandProperty] of shorthandFor) {
456
+ /**
457
+ * Helper to handle flexbox shorthand expansion.
458
+ *
459
+ * @private
460
+ * @param {string} property - The property name.
461
+ * @param {string} value - The property value.
462
+ * @param {string} priority - The priority.
463
+ * @param {string} shorthandProperty - The shorthand property name.
464
+ */
465
+ _flexBoxSetter(property, value, priority, shorthandProperty) {
466
+ if (!shorthandProperty || !shorthandProperties.has(shorthandProperty)) {
467
+ return;
468
+ }
469
+ const shorthandPriority = this._priorities.get(shorthandProperty);
470
+ this.removeProperty(shorthandProperty);
471
+ if (typeof priority !== "string") {
472
+ priority = this._priorities.get(property) ?? "";
473
+ }
474
+ this.removeProperty(property);
475
+ if (shorthandPriority && priority) {
476
+ this._setProperty(property, value);
477
+ } else {
478
+ this._setProperty(property, value, priority);
479
+ }
480
+ if (value && !hasVarFunc(value)) {
481
+ const longhandValues = [];
482
+ const shorthandItem = shorthandProperties.get(shorthandProperty);
483
+ let hasGlobalKeyword = false;
484
+ for (const [longhandProperty] of shorthandItem.shorthandFor) {
485
+ if (longhandProperty === property) {
486
+ if (isGlobalKeyword(value)) {
487
+ hasGlobalKeyword = true;
488
+ }
489
+ longhandValues.push(value);
490
+ } else {
611
491
  const longhandValue = this.getPropertyValue(longhandProperty);
612
492
  const longhandPriority = this._priorities.get(longhandProperty) ?? "";
613
493
  if (!longhandValue || longhandPriority !== priority) {
614
- return;
494
+ break;
495
+ }
496
+ if (isGlobalKeyword(longhandValue)) {
497
+ hasGlobalKeyword = true;
615
498
  }
616
499
  longhandValues.push(longhandValue);
617
500
  }
618
- if (longhandValues.length === shorthandFor.size) {
619
- const replacedValue = getPositionValue(longhandValues, shorthandPosition);
620
- this._setProperty(shorthandProperty, replacedValue);
501
+ }
502
+ if (longhandValues.length === shorthandItem.shorthandFor.size) {
503
+ if (hasGlobalKeyword) {
504
+ const [firstValue, ...restValues] = longhandValues;
505
+ if (restValues.every((val) => val === firstValue)) {
506
+ this._setProperty(shorthandProperty, firstValue, priority);
507
+ }
508
+ } else {
509
+ const parsedValue = shorthandItem.parse(longhandValues.join(" "));
510
+ const shorthandValue = Object.values(parsedValue).join(" ");
511
+ this._setProperty(shorthandProperty, shorthandValue, priority);
621
512
  }
622
513
  }
623
- },
624
- enumerable: false
514
+ }
625
515
  }
626
- });
627
516
 
628
- // Properties
629
- Object.defineProperties(CSSStyleDeclaration.prototype, generatedProperties);
517
+ /**
518
+ * Helper to handle position shorthand expansion.
519
+ *
520
+ * @private
521
+ * @param {string} property - The property name.
522
+ * @param {Array|string} value - The property value.
523
+ * @param {string} priority - The priority.
524
+ */
525
+ _positionShorthandSetter(property, value, priority) {
526
+ if (!shorthandProperties.has(property)) {
527
+ return;
528
+ }
529
+ const shorthandValues = [];
530
+ if (Array.isArray(value)) {
531
+ shorthandValues.push(...value);
532
+ } else if (typeof value === "string") {
533
+ shorthandValues.push(value);
534
+ } else {
535
+ return;
536
+ }
537
+ if (typeof priority !== "string") {
538
+ priority = this._priorities.get(property) ?? "";
539
+ }
540
+ const { position, shorthandFor } = shorthandProperties.get(property);
541
+ let hasPriority = false;
542
+ for (const [longhandProperty, longhandItem] of shorthandFor) {
543
+ const { position: longhandPosition } = longhandItem;
544
+ const longhandValue = getPositionValue(shorthandValues, longhandPosition);
545
+ if (priority) {
546
+ this._setProperty(longhandProperty, longhandValue, priority);
547
+ } else {
548
+ const longhandPriority = this._priorities.get(longhandProperty) ?? "";
549
+ if (longhandPriority) {
550
+ hasPriority = true;
551
+ } else {
552
+ this._setProperty(longhandProperty, longhandValue, priority);
553
+ }
554
+ }
555
+ }
556
+ if (hasPriority) {
557
+ this.removeProperty(property);
558
+ } else {
559
+ const shorthandValue = getPositionValue(shorthandValues, position);
560
+ this._setProperty(property, shorthandValue, priority);
561
+ }
562
+ }
630
563
 
631
- // Additional properties
632
- [...allProperties, ...allExtraProperties].forEach((property) => {
633
- if (!implementedProperties.has(property)) {
634
- const declaration = getPropertyDescriptor(property);
635
- Object.defineProperty(CSSStyleDeclaration.prototype, property, declaration);
636
- const camel = dashedToCamelCase(property);
637
- Object.defineProperty(CSSStyleDeclaration.prototype, camel, declaration);
638
- if (/^webkit[A-Z]/.test(camel)) {
639
- const pascal = camel.replace(/^webkit/, "Webkit");
640
- Object.defineProperty(CSSStyleDeclaration.prototype, pascal, declaration);
564
+ /**
565
+ * Helper to handle position longhand updates affecting shorthands.
566
+ *
567
+ * @private
568
+ * @param {string} property - The property name.
569
+ * @param {string} value - The property value.
570
+ * @param {string} priority - The priority.
571
+ * @param {string} shorthandProperty - The shorthand property name.
572
+ */
573
+ _positionLonghandSetter(property, value, priority, shorthandProperty) {
574
+ if (!shorthandProperty || !shorthandProperties.has(shorthandProperty)) {
575
+ return;
576
+ }
577
+ const shorthandPriority = this._priorities.get(shorthandProperty);
578
+ this.removeProperty(shorthandProperty);
579
+ if (typeof priority !== "string") {
580
+ priority = this._priorities.get(property) ?? "";
581
+ }
582
+ this.removeProperty(property);
583
+ if (shorthandPriority && priority) {
584
+ this._setProperty(property, value);
585
+ } else {
586
+ this._setProperty(property, value, priority);
587
+ }
588
+ if (value && !hasVarFunc(value)) {
589
+ const longhandValues = [];
590
+ const { shorthandFor, position: shorthandPosition } = shorthandProperties.get(shorthandProperty);
591
+ for (const [longhandProperty] of shorthandFor) {
592
+ const longhandValue = this.getPropertyValue(longhandProperty);
593
+ const longhandPriority = this._priorities.get(longhandProperty) ?? "";
594
+ if (!longhandValue || longhandPriority !== priority) {
595
+ return;
596
+ }
597
+ longhandValues.push(longhandValue);
598
+ }
599
+ if (longhandValues.length === shorthandFor.size) {
600
+ const replacedValue = getPositionValue(longhandValues, shorthandPosition);
601
+ this._setProperty(shorthandProperty, replacedValue);
602
+ }
641
603
  }
642
604
  }
643
- });
605
+ }
606
+
607
+ // TODO: Remove once the CSSStyleDeclaration is fully spec-compliant.
608
+ // @see https://github.com/jsdom/cssstyle/issues/255#issuecomment-3630183207
609
+ Object.defineProperties(CSSStyleDeclaration.prototype, propertyDescriptors);
644
610
 
645
611
  module.exports = {
646
- CSSStyleDeclaration,
647
- propertyList: Object.fromEntries(implementedProperties)
612
+ CSSStyleDeclaration
648
613
  };