cssstyle 3.0.0 → 4.0.1

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 CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  A Node JS implementation of the CSS Object Model [CSSStyleDeclaration interface](https://www.w3.org/TR/cssom-1/#the-cssstyledeclaration-interface).
4
4
 
5
- [![NpmVersion](https://img.shields.io/npm/v/cssstyle.svg)](https://www.npmjs.com/package/cssstyle) [![Build Status](https://travis-ci.org/jsdom/cssstyle.svg?branch=master)](https://travis-ci.org/jsdom/cssstyle) [![codecov](https://codecov.io/gh/jsdom/cssstyle/branch/master/graph/badge.svg)](https://codecov.io/gh/jsdom/cssstyle)
5
+ [![NpmVersion](https://img.shields.io/npm/v/cssstyle.svg)](https://www.npmjs.com/package/cssstyle) [![Build Status](https://travis-ci.org/jsdom/cssstyle.svg?branch=main)](https://travis-ci.org/jsdom/cssstyle) [![codecov](https://codecov.io/gh/jsdom/cssstyle/branch/main/graph/badge.svg)](https://codecov.io/gh/jsdom/cssstyle)
6
6
 
7
7
  ---
8
8
 
9
9
  #### Background
10
10
 
11
- This package is an extension of the CSSStyleDeclaration class in Nikita Vasilyev's [CSSOM](https://github.com/NV/CSSOM) with added support for CSS 2 & 3 properties. The primary use case is for testing browser code in a Node environment.
11
+ This package is an extension of the CSSStyleDeclaration class in Nikita Vasilyev's [CSSOM](https://github.com/NV/CSSOM) with added support for CSS 2 & 3 properties. The primary use case is for testing browser code in a Node environment.
12
12
 
13
13
  It was originally created by Chad Walker, it is now maintained by the jsdom community.
14
14
 
@@ -18,11 +18,8 @@ var CSSStyleDeclaration = function CSSStyleDeclaration(onChangeCallback) {
18
18
  this._values = {};
19
19
  this._importants = {};
20
20
  this._length = 0;
21
- this._onChange =
22
- onChangeCallback ||
23
- function () {
24
- return;
25
- };
21
+ this._onChange = onChangeCallback;
22
+ this._setInProgress = false;
26
23
  };
27
24
  CSSStyleDeclaration.prototype = {
28
25
  constructor: CSSStyleDeclaration,
@@ -77,6 +74,12 @@ CSSStyleDeclaration.prototype = {
77
74
  this.removeProperty(name);
78
75
  return;
79
76
  }
77
+
78
+ var originalText;
79
+ if (this._onChange) {
80
+ originalText = this.cssText;
81
+ }
82
+
80
83
  if (this._values[name]) {
81
84
  // Property already exist. Overwrite it.
82
85
  var index = Array.prototype.indexOf.call(this, name);
@@ -91,7 +94,9 @@ CSSStyleDeclaration.prototype = {
91
94
  }
92
95
  this._values[name] = value;
93
96
  this._importants[name] = priority;
94
- this._onChange(this.cssText);
97
+ if (this._onChange && this.cssText !== originalText && !this._setInProgress) {
98
+ this._onChange(this.cssText);
99
+ }
95
100
  },
96
101
 
97
102
  /**
@@ -121,7 +126,9 @@ CSSStyleDeclaration.prototype = {
121
126
  // That's what Firefox does
122
127
  //this[index] = ""
123
128
 
124
- this._onChange(this.cssText);
129
+ if (this._onChange) {
130
+ this._onChange(this.cssText);
131
+ }
125
132
  return prevValue;
126
133
  },
127
134
 
@@ -196,6 +203,7 @@ Object.defineProperties(CSSStyleDeclaration.prototype, {
196
203
  // malformed css, just return
197
204
  return;
198
205
  }
206
+ this._setInProgress = true;
199
207
  var rule_length = dummyRule.length;
200
208
  var name;
201
209
  for (i = 0; i < rule_length; ++i) {
@@ -206,7 +214,10 @@ Object.defineProperties(CSSStyleDeclaration.prototype, {
206
214
  dummyRule.getPropertyPriority(name)
207
215
  );
208
216
  }
209
- this._onChange(this.cssText);
217
+ this._setInProgress = false;
218
+ if (this._onChange) {
219
+ this._onChange(this.cssText);
220
+ }
210
221
  },
211
222
  enumerable: true,
212
223
  configurable: true,
@@ -346,6 +346,114 @@ describe('CSSStyleDeclaration', () => {
346
346
  testParts('margin', '1px 2px 3px 4px', 'auto');
347
347
  });
348
348
 
349
+ test('setting individual padding and margin properties to an empty string should clear them', () => {
350
+ var style = new CSSStyleDeclaration();
351
+
352
+ var properties = ['padding', 'margin'];
353
+ var parts = ['Top', 'Right', 'Bottom', 'Left'];
354
+ for (var i = 0; i < properties.length; i++) {
355
+ for (var j = 0; j < parts.length; j++) {
356
+ var property = properties[i] + parts[j];
357
+ style[property] = '12px';
358
+ expect(style[property]).toEqual('12px');
359
+
360
+ style[property] = '';
361
+ expect(style[property]).toEqual('');
362
+ }
363
+ }
364
+ });
365
+
366
+ test('removing and setting individual margin properties updates the combined property accordingly', () => {
367
+ var style = new CSSStyleDeclaration();
368
+ style.margin = '1px 2px 3px 4px';
369
+
370
+ style.marginTop = '';
371
+ expect(style.margin).toEqual('');
372
+ expect(style.marginRight).toEqual('2px');
373
+ expect(style.marginBottom).toEqual('3px');
374
+ expect(style.marginLeft).toEqual('4px');
375
+
376
+ style.marginBottom = '';
377
+ expect(style.margin).toEqual('');
378
+ expect(style.marginRight).toEqual('2px');
379
+ expect(style.marginLeft).toEqual('4px');
380
+
381
+ style.marginBottom = '5px';
382
+ expect(style.margin).toEqual('');
383
+ expect(style.marginRight).toEqual('2px');
384
+ expect(style.marginBottom).toEqual('5px');
385
+ expect(style.marginLeft).toEqual('4px');
386
+
387
+ style.marginTop = '6px';
388
+ expect(style.cssText).toEqual('margin: 6px 2px 5px 4px;');
389
+ });
390
+
391
+ test.each(['padding', 'margin'])(
392
+ 'removing an individual %s property should remove the combined property and replace it with the remaining individual ones',
393
+ (property) => {
394
+ var style = new CSSStyleDeclaration();
395
+ var parts = ['Top', 'Right', 'Bottom', 'Left'];
396
+ var partValues = ['1px', '2px', '3px', '4px'];
397
+
398
+ for (var j = 0; j < parts.length; j++) {
399
+ var partToRemove = parts[j];
400
+ style[property] = partValues.join(' ');
401
+ style[property + partToRemove] = '';
402
+
403
+ // Main property should have been removed
404
+ expect(style[property]).toEqual('');
405
+
406
+ // Expect other parts to still be there
407
+ for (var k = 0; k < parts.length; k++) {
408
+ var propertyCss = property + '-' + parts[k].toLowerCase() + ': ' + partValues[k] + ';';
409
+ if (k === j) {
410
+ expect(style[property + parts[k]]).toEqual('');
411
+ expect(style.cssText).not.toContain(propertyCss);
412
+ } else {
413
+ expect(style[property + parts[k]]).toEqual(partValues[k]);
414
+ expect(style.cssText).toContain(propertyCss);
415
+ }
416
+ }
417
+ }
418
+ }
419
+ );
420
+
421
+ test.each(['margin', 'padding'])(
422
+ 'setting additional %s properties keeps important status of others',
423
+ (property) => {
424
+ var style = new CSSStyleDeclaration();
425
+ var importantProperty = property + '-top: 3px !important;';
426
+ style.cssText = importantProperty;
427
+ expect(style.cssText).toContain(importantProperty);
428
+
429
+ style[property + 'Right'] = '4px';
430
+ style[property + 'Bottom'] = '5px';
431
+ style[property + 'Left'] = '6px';
432
+
433
+ expect(style.cssText).toContain(importantProperty);
434
+ expect(style.cssText).toContain(property + '-right: 4px;');
435
+ expect(style.cssText).toContain(property + '-bottom: 5px;');
436
+ expect(style.cssText).toContain(property + '-left: 6px;');
437
+ expect(style.cssText).not.toContain('margin:');
438
+ }
439
+ );
440
+
441
+ test.each(['margin', 'padding'])(
442
+ 'setting individual %s keeps important status of others',
443
+ (property) => {
444
+ var style = new CSSStyleDeclaration();
445
+ style.cssText = property + ': 3px !important;';
446
+
447
+ style[property + 'Top'] = '4px';
448
+
449
+ expect(style.cssText).toContain(property + '-top: 4px;');
450
+ expect(style.cssText).toContain(property + '-right: 3px !important;');
451
+ expect(style.cssText).toContain(property + '-bottom: 3px !important;');
452
+ expect(style.cssText).toContain(property + '-left: 3px !important;');
453
+ expect(style.cssText).not.toContain('margin:');
454
+ }
455
+ );
456
+
349
457
  test('setting a value to 0 should return the string value', () => {
350
458
  var style = new CSSStyleDeclaration();
351
459
  style.setProperty('fill-opacity', 0);
@@ -353,10 +461,46 @@ describe('CSSStyleDeclaration', () => {
353
461
  });
354
462
 
355
463
  test('onchange callback should be called when the csstext changes', () => {
464
+ var called = 0;
356
465
  var style = new CSSStyleDeclaration(function (cssText) {
466
+ called++;
357
467
  expect(cssText).toEqual('opacity: 0;');
358
468
  });
469
+ style.cssText = 'opacity: 0;';
470
+ expect(called).toEqual(1);
471
+ style.cssText = 'opacity: 0;';
472
+ expect(called).toEqual(2);
473
+ });
474
+
475
+ test('onchange callback should be called only once when multiple properties were added', () => {
476
+ var called = 0;
477
+ var style = new CSSStyleDeclaration(function (cssText) {
478
+ called++;
479
+ expect(cssText).toEqual('width: 100px; height: 100px;');
480
+ });
481
+ style.cssText = 'width: 100px;height:100px;';
482
+ expect(called).toEqual(1);
483
+ });
484
+
485
+ test('onchange callback should not be called when property is set to the same value', () => {
486
+ var called = 0;
487
+ var style = new CSSStyleDeclaration(function () {
488
+ called++;
489
+ });
490
+
491
+ style.setProperty('opacity', 0);
492
+ expect(called).toEqual(1);
359
493
  style.setProperty('opacity', 0);
494
+ expect(called).toEqual(1);
495
+ });
496
+
497
+ test('onchange callback should not be called when removeProperty was called on non-existing property', () => {
498
+ var called = 0;
499
+ var style = new CSSStyleDeclaration(function () {
500
+ called++;
501
+ });
502
+ style.removeProperty('opacity');
503
+ expect(called).toEqual(0);
360
504
  });
361
505
 
362
506
  test('setting float should work the same as cssfloat', () => {
@@ -402,6 +546,43 @@ describe('CSSStyleDeclaration', () => {
402
546
  expect(style.marginTop).toEqual('0.5ex');
403
547
  });
404
548
 
549
+ test('setting empty string and null to a padding or margin works', () => {
550
+ var style = new CSSStyleDeclaration();
551
+ var parts = ['Top', 'Right', 'Bottom', 'Left'];
552
+ function testParts(base, nullValue) {
553
+ var props = [base].concat(parts.map((part) => base + part));
554
+ for (let prop of props) {
555
+ expect(style[prop]).toEqual('');
556
+ style[prop] = '10px';
557
+ expect(style[prop]).toEqual('10px');
558
+ style[prop] = nullValue;
559
+ expect(style[prop]).toEqual('');
560
+ }
561
+ }
562
+
563
+ testParts('margin', '');
564
+ testParts('margin', null);
565
+ testParts('padding', '');
566
+ testParts('padding', null);
567
+ });
568
+
569
+ test('setting undefined to a padding or margin does nothing', () => {
570
+ var style = new CSSStyleDeclaration();
571
+ var parts = ['Top', 'Right', 'Bottom', 'Left'];
572
+ function testParts(base) {
573
+ var props = [base].concat(parts.map((part) => base + part));
574
+ for (let prop of props) {
575
+ style[prop] = '10px';
576
+ expect(style[prop]).toEqual('10px');
577
+ style[prop] = undefined;
578
+ expect(style[prop]).toEqual('10px');
579
+ }
580
+ }
581
+
582
+ testParts('margin');
583
+ testParts('padding');
584
+ });
585
+
405
586
  test('setting null to background works', () => {
406
587
  var style = new CSSStyleDeclaration();
407
588
  style.background = 'red';
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- // autogenerated - 2/12/2023
3
+ // autogenerated - 12/28/2023
4
4
 
5
5
  /*
6
6
  *
package/lib/parsers.js CHANGED
@@ -683,6 +683,9 @@ exports.subImplicitSetter = function (prefix, part, isValid, parser) {
683
683
  if (typeof v === 'number') {
684
684
  v = v.toString();
685
685
  }
686
+ if (v === null) {
687
+ v = '';
688
+ }
686
689
  if (typeof v !== 'string') {
687
690
  return undefined;
688
691
  }
@@ -691,19 +694,28 @@ exports.subImplicitSetter = function (prefix, part, isValid, parser) {
691
694
  }
692
695
  v = parser(v);
693
696
  this._setProperty(property, v);
694
- var parts = [];
695
- for (var i = 0; i < 4; i++) {
696
- if (this._values[subparts[i]] == null || this._values[subparts[i]] === '') {
697
- break;
698
- }
699
- parts.push(this._values[subparts[i]]);
700
- }
701
- if (parts.length === 4) {
702
- for (i = 0; i < 4; i++) {
697
+
698
+ var combinedPriority = this.getPropertyPriority(prefix);
699
+ var parts = subparts.map((subpart) => this._values[subpart]);
700
+ var priorities = subparts.map((subpart) => this.getPropertyPriority(subpart));
701
+ // Combine into a single property if all values are set and have the same priority
702
+ if (
703
+ parts.every((p) => p !== '' && p != null) &&
704
+ priorities.every((p) => p === priorities[0]) &&
705
+ priorities[0] === combinedPriority
706
+ ) {
707
+ for (var i = 0; i < subparts.length; i++) {
703
708
  this.removeProperty(subparts[i]);
704
709
  this._values[subparts[i]] = parts[i];
705
710
  }
706
- this._setProperty(prefix, parts.join(' '));
711
+ this._setProperty(prefix, parts.join(' '), priorities[0]);
712
+ } else {
713
+ this.removeProperty(prefix);
714
+ for (var j = 0; j < subparts.length; j++) {
715
+ // The property we're setting won't be important, the rest will either keep their priority or inherit it from the combined property
716
+ var priority = subparts[j] === property ? '' : priorities[j] || combinedPriority;
717
+ this._setProperty(subparts[j], parts[j], priority);
718
+ }
707
719
  }
708
720
  return v;
709
721
  };
@@ -107,7 +107,14 @@ describe('parseColor', () => {
107
107
 
108
108
  expect(output).toEqual('rgba(5, 5, 5, 0.5)');
109
109
  });
110
-
110
+ it.each([
111
+ [120, 'rgb(0, 255, 0)'],
112
+ [240, 'rgb(0, 0, 255)'],
113
+ ])('should convert not zero hsl with non zero hue %s to %s', (hue, rgbValue) => {
114
+ let input = 'hsl(' + hue + ', 100%, 50%)';
115
+ let output = parsers.parseColor(input);
116
+ expect(output).toEqual(rgbValue);
117
+ });
111
118
  it.todo('Add more tests');
112
119
  });
113
120
  describe('parseAngle', () => {
@@ -9,6 +9,7 @@ var isValid = function (v) {
9
9
  }
10
10
  var type = parsers.valueType(v);
11
11
  return (
12
+ type === TYPES.NULL_OR_EMPTY_STR ||
12
13
  type === TYPES.LENGTH ||
13
14
  type === TYPES.PERCENT ||
14
15
  (type === TYPES.INTEGER && (v === '0' || v === 0))
@@ -40,6 +41,9 @@ module.exports.definition = {
40
41
  if (typeof v === 'number') {
41
42
  v = String(v);
42
43
  }
44
+ if (v === null) {
45
+ v = '';
46
+ }
43
47
  if (typeof v !== 'string') {
44
48
  return;
45
49
  }
@@ -6,6 +6,7 @@ var TYPES = parsers.TYPES;
6
6
  var isValid = function (v) {
7
7
  var type = parsers.valueType(v);
8
8
  return (
9
+ type === TYPES.NULL_OR_EMPTY_STR ||
9
10
  type === TYPES.LENGTH ||
10
11
  type === TYPES.PERCENT ||
11
12
  (type === TYPES.INTEGER && (v === '0' || v === 0))
@@ -33,6 +34,9 @@ module.exports.definition = {
33
34
  if (typeof v === 'number') {
34
35
  v = String(v);
35
36
  }
37
+ if (v === null) {
38
+ v = '';
39
+ }
36
40
  if (typeof v !== 'string') {
37
41
  return;
38
42
  }
package/lib/properties.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- // autogenerated - 2/12/2023
3
+ // autogenerated - 12/28/2023
4
4
 
5
5
  /*
6
6
  *
@@ -1225,7 +1225,7 @@ var margin_local_var_isValid = function (v) {
1225
1225
  }
1226
1226
 
1227
1227
  var type = external_dependency_parsers_0.valueType(v);
1228
- return type === margin_local_var_TYPES.LENGTH || type === margin_local_var_TYPES.PERCENT || type === margin_local_var_TYPES.INTEGER && (v === '0' || v === 0);
1228
+ return type === margin_local_var_TYPES.NULL_OR_EMPTY_STR || type === margin_local_var_TYPES.LENGTH || type === margin_local_var_TYPES.PERCENT || type === margin_local_var_TYPES.INTEGER && (v === '0' || v === 0);
1229
1229
  };
1230
1230
 
1231
1231
  var margin_local_var_parser = function (v) {
@@ -1250,6 +1250,10 @@ margin_export_definition = {
1250
1250
  v = String(v);
1251
1251
  }
1252
1252
 
1253
+ if (v === null) {
1254
+ v = '';
1255
+ }
1256
+
1253
1257
  if (typeof v !== 'string') {
1254
1258
  return;
1255
1259
  }
@@ -1372,7 +1376,7 @@ var padding_local_var_TYPES = external_dependency_parsers_0.TYPES;
1372
1376
 
1373
1377
  var padding_local_var_isValid = function (v) {
1374
1378
  var type = external_dependency_parsers_0.valueType(v);
1375
- return type === padding_local_var_TYPES.LENGTH || type === padding_local_var_TYPES.PERCENT || type === padding_local_var_TYPES.INTEGER && (v === '0' || v === 0);
1379
+ return type === padding_local_var_TYPES.NULL_OR_EMPTY_STR || type === padding_local_var_TYPES.LENGTH || type === padding_local_var_TYPES.PERCENT || type === padding_local_var_TYPES.INTEGER && (v === '0' || v === 0);
1376
1380
  };
1377
1381
 
1378
1382
  var padding_local_var_parser = function (v) {
@@ -1391,6 +1395,10 @@ padding_export_definition = {
1391
1395
  v = String(v);
1392
1396
  }
1393
1397
 
1398
+ if (v === null) {
1399
+ v = '';
1400
+ }
1401
+
1394
1402
  if (typeof v !== 'string') {
1395
1403
  return;
1396
1404
  }
@@ -1,21 +1,19 @@
1
1
  'use strict';
2
2
 
3
- const hueToRgb = (t1, t2, hue) => {
4
- if (hue < 0) hue += 6;
5
- if (hue >= 6) hue -= 6;
6
-
7
- if (hue < 1) return (t2 - t1) * hue + t1;
8
- else if (hue < 3) return t2;
9
- else if (hue < 4) return (t2 - t1) * (4 - hue) + t1;
10
- else return t1;
11
- };
3
+ const MAX_HUE = 360;
4
+ const COLOR_NB = 12;
5
+ const MAX_RGB_VALUE = 255;
12
6
 
13
7
  // https://www.w3.org/TR/css-color-4/#hsl-to-rgb
14
8
  exports.hslToRgb = (hue, sat, light) => {
15
- const t2 = light <= 0.5 ? light * (sat + 1) : light + sat - light * sat;
16
- const t1 = light * 2 - t2;
17
- const r = hueToRgb(t1, t2, hue + 2);
18
- const g = hueToRgb(t1, t2, hue);
19
- const b = hueToRgb(t1, t2, hue - 2);
20
- return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
9
+ hue = hue % MAX_HUE;
10
+ if (hue < 0) {
11
+ hue += MAX_HUE;
12
+ }
13
+ function f(n) {
14
+ const k = (n + hue / (MAX_HUE / COLOR_NB)) % COLOR_NB;
15
+ const a = sat * Math.min(light, 1 - light);
16
+ return light - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
17
+ }
18
+ return [f(0), f(8), f(4)].map((value) => Math.round(value * MAX_RGB_VALUE));
21
19
  };
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "CSSStyleDeclaration",
7
7
  "StyleSheet"
8
8
  ],
9
- "version": "3.0.0",
9
+ "version": "4.0.1",
10
10
  "homepage": "https://github.com/jsdom/cssstyle",
11
11
  "maintainers": [
12
12
  {
@@ -44,13 +44,12 @@
44
44
  "babel-traverse": "^6.26.0",
45
45
  "babel-types": "^6.26.0",
46
46
  "babylon": "^6.18.0",
47
- "eslint": "^8.32.0",
48
- "eslint-config-prettier": "^8.6.0",
49
- "eslint-plugin-prettier": "^4.2.1",
50
- "jest": "^29.3.1",
51
- "minipass-fetch": "^3.0.1",
47
+ "eslint": "^8.56.0",
48
+ "eslint-config-prettier": "^9.1.0",
49
+ "eslint-plugin-prettier": "^5.1.2",
50
+ "jest": "^29.7.0",
52
51
  "npm-run-all": "^4.1.5",
53
- "prettier": "^2.8.3",
52
+ "prettier": "^3.1.1",
54
53
  "resolve": "^1.22.1"
55
54
  },
56
55
  "scripts": {
@@ -67,6 +66,6 @@
67
66
  },
68
67
  "license": "MIT",
69
68
  "engines": {
70
- "node": ">=14"
69
+ "node": ">=18"
71
70
  }
72
71
  }