postcss-minify-gradients 8.0.3 → 8.0.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postcss-minify-gradients",
3
- "version": "8.0.3",
3
+ "version": "8.0.5",
4
4
  "description": "Minify gradient parameters with PostCSS.",
5
5
  "exports": {
6
6
  ".": {
@@ -31,12 +31,12 @@
31
31
  },
32
32
  "repository": {
33
33
  "type": "git",
34
- "url": "cssnano/cssnano"
34
+ "url": "git+https://github.com/cssnano/cssnano.git"
35
35
  },
36
36
  "dependencies": {
37
37
  "@colordx/core": "^5.5.0",
38
38
  "postcss-value-parser": "^4.2.0",
39
- "cssnano-utils": "^6.0.3"
39
+ "cssnano-utils": "^6.0.4"
40
40
  },
41
41
  "bugs": {
42
42
  "url": "https://github.com/cssnano/cssnano/issues"
package/src/index.js CHANGED
@@ -11,7 +11,7 @@ const directionsToAngles = new Map([
11
11
  ]);
12
12
 
13
13
  /**
14
- * Returns whether b is less than a.
14
+ * Returns whether b is less than or equal to a.
15
15
  *
16
16
  * @param {valueParser.Dimension} a
17
17
  * @param {valueParser.Dimension} b
@@ -20,108 +20,217 @@ const directionsToAngles = new Map([
20
20
  function isLessThan(a, b) {
21
21
  return (
22
22
  a.unit.toLowerCase() === b.unit.toLowerCase() &&
23
- parseFloat(a.number) >= parseFloat(b.number)
23
+ Number.parseFloat(a.number) >= Number.parseFloat(b.number)
24
24
  );
25
25
  }
26
26
 
27
27
  /**
28
- * Shortens a direction like `to left top` into an angle.
28
+ * Compares positions when their ordering is independent of layout.
29
29
  *
30
- * @param {import('postcss-value-parser').FunctionNode} node
31
- * @returns {void}
30
+ * @param {valueParser.Dimension} a
31
+ * @param {valueParser.Dimension} b
32
+ * @returns {boolean | undefined}
32
33
  */
33
- function shortenDirection(node) {
34
- node.nodes = node.nodes.slice(2);
35
- node.nodes[0].value = /** @type {string} */ (
36
- directionsToAngles.get(node.nodes[0].value.toLowerCase())
37
- );
34
+ function isLessThanOrEqual(a, b) {
35
+ if (a.unit.toLowerCase() === b.unit.toLowerCase()) {
36
+ return isLessThan(a, b);
37
+ }
38
+
39
+ const aNumber = Number.parseFloat(a.number);
40
+ const bNumber = Number.parseFloat(b.number);
41
+
42
+ if (aNumber === 0) {
43
+ return bNumber <= 0;
44
+ }
45
+
46
+ if (bNumber === 0) {
47
+ return aNumber >= 0;
48
+ }
38
49
  }
39
50
 
40
51
  /**
41
- * Optimises a linear gradient.
52
+ * Returns whether a node is a literal color stop position.
42
53
  *
43
- * @param {import('postcss-value-parser').FunctionNode} node
44
- * @returns {false}
54
+ * @param {import('postcss-value-parser').Node} node
55
+ * @returns {boolean}
45
56
  */
46
- function optimizeLinearGradient(node) {
47
- const args = getArguments(node);
48
- if (node.nodes[0].value.toLowerCase() === 'to' && args[0].length === 3) {
49
- shortenDirection(node);
57
+ function isPosition(node) {
58
+ return node.type === 'word' && valueParser.unit(node.value) !== false;
59
+ }
60
+
61
+ /**
62
+ * Returns whether an argument begins a color stop.
63
+ *
64
+ * @param {import('postcss-value-parser').Node[]} argument
65
+ * @returns {boolean}
66
+ */
67
+ function isColorStopArgument(argument) {
68
+ const first = argument[0];
69
+
70
+ if (!first) {
71
+ return false;
50
72
  }
51
- /** @type {valueParser.Dimension | false | undefined} */
52
- let previousStop = undefined;
53
73
 
54
- for (const [index, arg] of args.entries()) {
55
- /* Check whether the stop contains a position */
56
- if (arg.length !== 3) {
57
- continue;
58
- }
74
+ if (first.type === 'function') {
75
+ return !new Set(['calc', 'clamp', 'max', 'min']).has(
76
+ first.value.toLowerCase()
77
+ );
78
+ }
59
79
 
60
- const isFinalStop = index === args.length - 1;
61
- const thisStop = valueParser.unit(arg[2].value);
80
+ return first.type === 'word' && isColorStop(first.value);
81
+ }
62
82
 
63
- if (previousStop === undefined) {
64
- previousStop = thisStop;
83
+ /**
84
+ * Gets the position nodes from a color stop or transition hint.
85
+ *
86
+ * @param {import('postcss-value-parser').Node[]} argument
87
+ * @param {boolean} colorStopArgument
88
+ * @returns {import('postcss-value-parser').Node[]}
89
+ */
90
+ function getPositions(argument, colorStopArgument) {
91
+ return argument.slice(colorStopArgument ? 1 : 0).filter((node) => {
92
+ return node.type !== 'space' && node.type !== 'comment';
93
+ });
94
+ }
65
95
 
66
- if (
67
- !isFinalStop &&
68
- previousStop &&
69
- args.length < 4 &&
70
- previousStop.number === '0' &&
71
- previousStop.unit.toLowerCase() !== 'deg'
72
- ) {
73
- arg[1].value = arg[2].value = '';
74
- }
96
+ /**
97
+ * Updates the largest preceding position, replacing a position that will be
98
+ * fixed up with its shorter equivalent.
99
+ *
100
+ * @param {valueParser.Dimension | undefined} largestPosition
101
+ * @param {import('postcss-value-parser').Node} position
102
+ * @returns {valueParser.Dimension | undefined}
103
+ */
104
+ function updateLargestPosition(largestPosition, position) {
105
+ if (!isPosition(position)) {
106
+ return undefined;
107
+ }
75
108
 
76
- continue;
77
- }
109
+ const currentPosition = valueParser.unit(position.value);
78
110
 
79
- if (previousStop && thisStop && isLessThan(previousStop, thisStop)) {
80
- arg[2].value = '0';
81
- }
111
+ if (!currentPosition) {
112
+ return undefined;
113
+ }
82
114
 
83
- previousStop = thisStop;
115
+ if (!largestPosition) {
116
+ return currentPosition;
117
+ }
84
118
 
85
- if (isFinalStop && arg[2].value === '100%') {
86
- arg[1].value = arg[2].value = '';
87
- }
119
+ const isFixedUp = isLessThanOrEqual(largestPosition, currentPosition);
120
+
121
+ if (isFixedUp) {
122
+ position.value = '0';
123
+ return largestPosition;
88
124
  }
89
125
 
90
- return false;
126
+ return isFixedUp === undefined ? undefined : currentPosition;
91
127
  }
92
128
 
93
129
  /**
94
- * Optimises a radial gradient.
130
+ * Optimizes a standard gradient color stop list using the color stop fixup
131
+ * algorithm. A non-literal position prevents later comparisons because its
132
+ * used value can only be known during layout.
95
133
  *
96
- * @param {import('postcss-value-parser').ParsedValue | import('postcss-value-parser').FunctionNode} node
97
- * @returns {false}
134
+ * @param {import('postcss-value-parser').Node[][]} args
135
+ * @returns {void}
98
136
  */
99
- function optimizeRadialGradient(node) {
100
- const args = getArguments(node);
101
- /** @type {valueParser.Dimension | false | undefined} */
102
- let previousStop = undefined;
137
+ function optimizeColorStops(args) {
138
+ const firstStopIndex = args.findIndex(isColorStopArgument);
103
139
 
104
- const hasAt = args[0].find((n) => n.value.toLowerCase() === 'at');
140
+ if (firstStopIndex === -1) {
141
+ return;
142
+ }
105
143
 
106
- for (const [index, arg] of args.entries()) {
107
- if (!arg[2] || (!index && hasAt)) {
108
- continue;
109
- }
144
+ const stops = [];
145
+ let largestPosition = /** @type {valueParser.Dimension | undefined} */ (
146
+ undefined
147
+ );
148
+ let hasSeenStop = false;
110
149
 
111
- const thisStop = valueParser.unit(arg[2].value);
150
+ for (const argument of args.slice(firstStopIndex)) {
151
+ const colorStop = isColorStopArgument(argument);
152
+ const positions = getPositions(argument, colorStop);
112
153
 
113
- if (!previousStop) {
114
- previousStop = thisStop;
154
+ if (colorStop) {
155
+ stops.push({ argument, positions });
156
+
157
+ if (!hasSeenStop && positions.length === 0) {
158
+ largestPosition = /** @type {valueParser.Dimension} */ (
159
+ valueParser.unit('0%')
160
+ );
161
+ }
162
+
163
+ hasSeenStop = true;
164
+ }
115
165
 
166
+ if (positions.length === 0) {
116
167
  continue;
117
168
  }
118
169
 
119
- if (previousStop && thisStop && isLessThan(previousStop, thisStop)) {
120
- arg[2].value = '0';
170
+ for (const position of positions) {
171
+ largestPosition = updateLargestPosition(largestPosition, position);
121
172
  }
173
+ }
122
174
 
123
- previousStop = thisStop;
175
+ const firstStop = stops[0];
176
+ const lastStop = stops.at(-1);
177
+
178
+ if (
179
+ firstStop &&
180
+ firstStop.positions.length === 1 &&
181
+ firstStop.argument.length === 3 &&
182
+ firstStop.positions[0].value === '0%'
183
+ ) {
184
+ firstStop.argument[1].value = firstStop.positions[0].value = '';
185
+ }
186
+
187
+ if (
188
+ lastStop &&
189
+ lastStop.positions.length === 1 &&
190
+ lastStop.argument.length === 3 &&
191
+ lastStop.positions[0].value === '100%'
192
+ ) {
193
+ lastStop.argument[1].value = lastStop.positions[0].value = '';
124
194
  }
195
+ }
196
+
197
+ /**
198
+ * Shortens a direction like `to left top` into an angle.
199
+ *
200
+ * @param {import('postcss-value-parser').FunctionNode} node
201
+ * @returns {void}
202
+ */
203
+ function shortenDirection(node) {
204
+ node.nodes = node.nodes.slice(2);
205
+ node.nodes[0].value = /** @type {string} */ (
206
+ directionsToAngles.get(node.nodes[0].value.toLowerCase())
207
+ );
208
+ }
209
+
210
+ /**
211
+ * Optimises a linear gradient.
212
+ *
213
+ * @param {import('postcss-value-parser').FunctionNode} node
214
+ * @returns {false}
215
+ */
216
+ function optimizeLinearGradient(node) {
217
+ const args = getArguments(node);
218
+ if (node.nodes[0]?.value.toLowerCase() === 'to' && args[0].length === 3) {
219
+ shortenDirection(node);
220
+ }
221
+ optimizeColorStops(getArguments(node));
222
+
223
+ return false;
224
+ }
225
+
226
+ /**
227
+ * Optimises a radial gradient.
228
+ *
229
+ * @param {import('postcss-value-parser').ParsedValue | import('postcss-value-parser').FunctionNode} node
230
+ * @returns {false}
231
+ */
232
+ function optimizeRadialGradient(node) {
233
+ optimizeColorStops(getArguments(node));
125
234
 
126
235
  return false;
127
236
  }
@@ -242,6 +351,13 @@ function optimise(decl) {
242
351
  ) {
243
352
  return optimizeWebkitRadialGradient(node);
244
353
  }
354
+
355
+ if (
356
+ lowerCasedValue === 'conic-gradient' ||
357
+ lowerCasedValue === 'repeating-conic-gradient'
358
+ ) {
359
+ optimizeColorStops(getArguments(node));
360
+ }
245
361
  return false;
246
362
  })
247
363
  .toString();
@@ -4,7 +4,14 @@ const { colordx: colord, extend } = require('@colordx/core');
4
4
  const hwbPlugin = require('@colordx/core/plugins/hwb');
5
5
  const namesPlugin = require('@colordx/core/plugins/names');
6
6
 
7
- extend([/** @type {any} */ (hwbPlugin), /** @type {any} */ (namesPlugin)]);
7
+ extend([
8
+ /** @type {import('@colordx/core').Plugin} */ (
9
+ /** @type {unknown} */ (hwbPlugin)
10
+ ),
11
+ /** @type {import('@colordx/core').Plugin} */ (
12
+ /** @type {unknown} */ (namesPlugin)
13
+ ),
14
+ ]);
8
15
 
9
16
  /* Code derived from https://www.npmjs.com/package/is-color-stop Licensed under MIT license */
10
17
 
@@ -45,7 +52,10 @@ function isStop(str) {
45
52
  const node = unit(str);
46
53
  if (node) {
47
54
  const number = Number(node.number);
48
- if (number === 0 || (!isNaN(number) && isCSSLengthUnit(node.unit))) {
55
+ if (
56
+ number === 0 ||
57
+ (!Number.isNaN(number) && isCSSLengthUnit(node.unit))
58
+ ) {
49
59
  isColorStop = true;
50
60
  }
51
61
  } else {
@@ -1 +1 @@
1
- {"version":3,"file":"isColorStop.d.ts","sourceRoot":"","sources":["../src/isColorStop.js"],"names":[],"mappings":";AAyDA;;;;GAIG;6BACmC,KAAK,EAJhC,MAIgC,EAAE,IAAI,AAH9C,CACA,EADQ,MAGsC,GAFrC,OAAO"}
1
+ {"version":3,"file":"isColorStop.d.ts","sourceRoot":"","sources":["../src/isColorStop.js"],"names":[],"mappings":";AAmEA;;;;GAIG;6BACmC,KAAK,EAJhC,MAIgC,EAAE,IAAI,AAH9C,CACA,EADQ,MAGsC,GAFrC,OAAO"}