postcss-minify-gradients 9.0.4 → 9.0.6

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": "9.0.4",
3
+ "version": "9.0.6",
4
4
  "description": "Minify gradient parameters with PostCSS.",
5
5
  "exports": {
6
6
  ".": {
@@ -35,7 +35,7 @@
35
35
  "dependencies": {
36
36
  "@colordx/core": "^7.2.0",
37
37
  "@csstools/css-tokenizer": "^4.0.2",
38
- "cssnano-utils": "^8.0.0"
38
+ "cssnano-utils": "^8.0.2"
39
39
  },
40
40
  "bugs": {
41
41
  "url": "https://github.com/cssnano/cssnano/issues"
package/src/index.js CHANGED
@@ -1,16 +1,11 @@
1
1
  /** @import {CSSToken} from '@csstools/css-tokenizer' */
2
+ /** @import {SourceEdit, PositionValue} from './lib/positions.js' */
2
3
  import cssnanoUtils from 'cssnano-utils';
3
4
  import isKnownColor from './isKnownColor.js';
5
+ import { isPositionZeroUnit, zeroPositionEdits } from './lib/positions.js';
4
6
 
5
- const {
6
- TokenType,
7
- applyEdits,
8
- asciiLowerCase,
9
- decoded,
10
- lengthUnits,
11
- numeric,
12
- tokenEnd,
13
- } = cssnanoUtils;
7
+ const { TokenType, applyEdits, asciiLowerCase, decoded, numeric, tokenEnd } =
8
+ cssnanoUtils;
14
9
  /** @type {typeof cssnanoUtils.balancedTokens} */
15
10
  const balancedTokens = cssnanoUtils.balancedTokens;
16
11
 
@@ -57,7 +52,6 @@ const lineKeywords = new Set([
57
52
  'top',
58
53
  ]);
59
54
 
60
- /** @typedef {{start: number, end: number, text: string}} SourceEdit */
61
55
  /**
62
56
  * A gradient stop before fixup: the argument it came from, the extent of its
63
57
  * colour, and the position tokens following that colour.
@@ -67,11 +61,6 @@ const lineKeywords = new Set([
67
61
  * @property {number} colorEnd Offset just past the colour in the declaration value.
68
62
  * @property {readonly number[]} position Positions following the colour.
69
63
  */
70
- /**
71
- * A position as a number and its unit, as reported by `numeric()`.
72
- *
73
- * @typedef {{number: number, unit: string}} PositionValue
74
- */
75
64
 
76
65
  /**
77
66
  * Significant tokens of a range, skipping whitespace, comments and the contents
@@ -120,38 +109,6 @@ function startsLineSpecification(input, parts) {
120
109
  );
121
110
  }
122
111
 
123
- /**
124
- * A zero length and a zero percentage name the same gradient position, which is
125
- * also what the unitless zero this plugin emits denotes. Angles and other
126
- * dimensions only share a spelling with it.
127
- *
128
- * @param {string} unit
129
- * @return {boolean}
130
- */
131
- function isPositionZeroUnit(unit) {
132
- const lowered = asciiLowerCase(unit);
133
- return lowered === '' || lowered === '%' || lengthUnits.has(lowered);
134
- }
135
-
136
- /**
137
- * Whether two positions are on the same scale, so their numbers can be ordered.
138
- * Only a length and a percentage naming zero are interchangeable.
139
- *
140
- * @param {PositionValue} current
141
- * @param {PositionValue} largest
142
- * @return {boolean}
143
- */
144
- function positionsComparable(current, largest) {
145
- const unit = asciiLowerCase(current.unit);
146
- const largestUnit = asciiLowerCase(largest.unit);
147
- if (unit === largestUnit) return true;
148
- return (
149
- (current.number === 0 || largest.number === 0) &&
150
- isPositionZeroUnit(unit) &&
151
- isPositionZeroUnit(largestUnit)
152
- );
153
- }
154
-
155
112
  /**
156
113
  * Offset just past the leading colour of an argument, so a functional colour
157
114
  * such as `rgb(0 0 0 / 50%)` ends at its closing parenthesis.
@@ -191,50 +148,6 @@ function lineDirectionEdit(input, parts) {
191
148
  return { start: from[2], end: tokenEnd(side), text: direction };
192
149
  }
193
150
 
194
- /**
195
- * Colour stop fixup raises a position to the largest position before it, so a
196
- * position at or below that non-negative maximum can be written as a zero.
197
- *
198
- * @param {readonly CSSToken[]} input
199
- * @param {readonly number[]} position Offsets of the position tokens.
200
- * @param {PositionValue | undefined} maximum Running maximum of the preceding positions.
201
- * @param {Map<number, SourceEdit>} edits Collects the positions reduced to a zero.
202
- * @return {PositionValue | undefined} The maximum the following stops clamp to.
203
- */
204
- function zeroPositionEdits(input, position, maximum, edits) {
205
- let largest = maximum;
206
- for (const index of position) {
207
- const token = input[index];
208
- const current = numeric(token);
209
- if (!current) {
210
- largest = undefined;
211
- continue;
212
- }
213
- // A zero clamps to any non-negative maximum whatever units name them, so
214
- // the maximum survives the rewritten spelling instead of resetting on the
215
- // next pass.
216
- if (
217
- largest &&
218
- largest.number >= 0 &&
219
- current.number === 0 &&
220
- isPositionZeroUnit(current.unit)
221
- ) {
222
- edits.set(index, { start: token[2], end: tokenEnd(token), text: '0' });
223
- continue;
224
- }
225
- if (largest && !positionsComparable(current, largest)) {
226
- largest = undefined;
227
- continue;
228
- }
229
- if (largest && largest.number >= 0 && largest.number >= current.number) {
230
- edits.set(index, { start: token[2], end: tokenEnd(token), text: '0' });
231
- } else {
232
- largest = current;
233
- }
234
- }
235
- return largest;
236
- }
237
-
238
151
  /**
239
152
  * Collect the colour stops of one gradient argument list, the count of leading
240
153
  * arguments naming the line or shape instead of a stop, and the positions that
@@ -245,9 +158,10 @@ function zeroPositionEdits(input, position, maximum, edits) {
245
158
  * @param {NonNullable<ReturnType<typeof balancedTokens>>} structure
246
159
  * @param {{startIndex: number, endIndex: number}[]} args
247
160
  * @param {boolean} linear Whether the line specification may hold an angle.
161
+ * @param {boolean} conic Whether the stop positions are angles.
248
162
  * @return {{stops: ColorStop[], lineSpecifications: number, zeroEdits: Map<number, SourceEdit>, directionEdit: SourceEdit | undefined}}
249
163
  */
250
- function collectColorStops(source, input, structure, args, linear) {
164
+ function collectColorStops(source, input, structure, args, linear, conic) {
251
165
  /** @type {ColorStop[]} */
252
166
  const stops = [];
253
167
  /** @type {Map<number, SourceEdit>} */
@@ -285,7 +199,7 @@ function collectColorStops(source, input, structure, args, linear) {
285
199
  }
286
200
  // Positions before the first stop belong to the line specification.
287
201
  if (!stops.length) continue;
288
- largest = zeroPositionEdits(input, position, largest, zeroEdits);
202
+ largest = zeroPositionEdits(input, position, largest, zeroEdits, conic);
289
203
  }
290
204
  return { stops, lineSpecifications, zeroEdits, directionEdit };
291
205
  }
@@ -301,9 +215,17 @@ function collectColorStops(source, input, structure, args, linear) {
301
215
  * @param {number} lineSpecifications
302
216
  * @param {number} args
303
217
  * @param {Map<number, SourceEdit>} zeroEdits
218
+ * @param {boolean} conic
304
219
  * @return {SourceEdit[]}
305
220
  */
306
- function boundaryStopEdits(input, stops, lineSpecifications, args, zeroEdits) {
221
+ function boundaryStopEdits(
222
+ input,
223
+ stops,
224
+ lineSpecifications,
225
+ args,
226
+ zeroEdits,
227
+ conic
228
+ ) {
307
229
  /** @type {SourceEdit[]} */
308
230
  const edits = [];
309
231
  for (const slot of new Set([0, stops.length - 1])) {
@@ -315,7 +237,7 @@ function boundaryStopEdits(input, stops, lineSpecifications, args, zeroEdits) {
315
237
  if (!value) continue;
316
238
  let removable = false;
317
239
  if (slot === 0 && stop.argIndex === lineSpecifications) {
318
- removable = value.number === 0 && isPositionZeroUnit(value.unit);
240
+ removable = value.number === 0 && isPositionZeroUnit(value.unit, conic);
319
241
  }
320
242
  if (
321
243
  slot === stops.length - 1 &&
@@ -367,14 +289,12 @@ function optimise(decl) {
367
289
  const end = structure.endForOpening(index);
368
290
  if (end === undefined) continue;
369
291
  const args = structure.topLevelSegments(index + 1, end);
292
+ // The legacy prefixed syntax has no `to <side>` form, where an angle
293
+ // would also point in the opposite sense.
294
+ const linear = name.includes('linear') && !name.startsWith('-webkit-');
295
+ const conic = name.includes('conic');
370
296
  const { stops, lineSpecifications, zeroEdits, directionEdit } =
371
- collectColorStops(
372
- source,
373
- input,
374
- structure,
375
- args,
376
- name.includes('linear')
377
- );
297
+ collectColorStops(source, input, structure, args, linear, conic);
378
298
  if (directionEdit) replacements.push(directionEdit);
379
299
  replacements.push(
380
300
  ...boundaryStopEdits(
@@ -382,7 +302,8 @@ function optimise(decl) {
382
302
  stops,
383
303
  lineSpecifications,
384
304
  args.length,
385
- zeroEdits
305
+ zeroEdits,
306
+ conic
386
307
  ),
387
308
  ...zeroEdits.values()
388
309
  );
@@ -0,0 +1,99 @@
1
+ /** @import {CSSToken} from '@csstools/css-tokenizer' */
2
+ import cssnanoUtils from 'cssnano-utils';
3
+
4
+ const { asciiLowerCase, lengthUnits, numeric, tokenEnd } = cssnanoUtils;
5
+
6
+ /** @typedef {{start: number, end: number, text: string}} SourceEdit */
7
+
8
+ /**
9
+ * A position as a number and its unit, as reported by `numeric()`.
10
+ *
11
+ * @typedef {{number: number, unit: string}} PositionValue
12
+ */
13
+
14
+ const angleUnits = new Set(['deg', 'grad', 'rad', 'turn']);
15
+
16
+ /**
17
+ * A zero in the position's own dimension and a zero percentage name the same
18
+ * gradient position, which is also what the unitless zero this plugin emits
19
+ * denotes. Conic positions are angles and the other gradients' are lengths; a
20
+ * zero of the other dimension is invalid there, so it must not be rewritten
21
+ * into a valid one.
22
+ *
23
+ * @param {string} unit
24
+ * @param {boolean} conic Whether the positions are `<angle-percentage>`.
25
+ * @return {boolean}
26
+ */
27
+ export function isPositionZeroUnit(unit, conic) {
28
+ const lowered = asciiLowerCase(unit);
29
+ return (
30
+ lowered === '' ||
31
+ lowered === '%' ||
32
+ (conic ? angleUnits : lengthUnits).has(lowered)
33
+ );
34
+ }
35
+
36
+ /**
37
+ * Whether two positions are on the same scale, so their numbers can be ordered.
38
+ * Only a length and a percentage naming zero are interchangeable.
39
+ *
40
+ * @param {PositionValue} current
41
+ * @param {PositionValue} largest
42
+ * @param {boolean} conic
43
+ * @return {boolean}
44
+ */
45
+ export function positionsComparable(current, largest, conic) {
46
+ const unit = asciiLowerCase(current.unit);
47
+ const largestUnit = asciiLowerCase(largest.unit);
48
+ if (unit === largestUnit) return true;
49
+ return (
50
+ (current.number === 0 || largest.number === 0) &&
51
+ isPositionZeroUnit(unit, conic) &&
52
+ isPositionZeroUnit(largestUnit, conic)
53
+ );
54
+ }
55
+
56
+ /**
57
+ * Colour stop fixup raises a position to the largest position before it, so a
58
+ * position at or below that non-negative maximum can be written as a zero.
59
+ *
60
+ * @param {readonly CSSToken[]} input
61
+ * @param {readonly number[]} position Offsets of the position tokens.
62
+ * @param {PositionValue | undefined} maximum Running maximum of the preceding positions.
63
+ * @param {Map<number, SourceEdit>} edits Collects the positions reduced to a zero.
64
+ * @param {boolean} conic
65
+ * @return {PositionValue | undefined} The maximum the following stops clamp to.
66
+ */
67
+ export function zeroPositionEdits(input, position, maximum, edits, conic) {
68
+ let largest = maximum;
69
+ for (const index of position) {
70
+ const token = input[index];
71
+ const current = numeric(token);
72
+ if (!current) {
73
+ largest = undefined;
74
+ continue;
75
+ }
76
+ // A zero clamps to any non-negative maximum whatever units name them, so
77
+ // the maximum survives the rewritten spelling instead of resetting on the
78
+ // next pass.
79
+ if (
80
+ largest &&
81
+ largest.number >= 0 &&
82
+ current.number === 0 &&
83
+ isPositionZeroUnit(current.unit, conic)
84
+ ) {
85
+ edits.set(index, { start: token[2], end: tokenEnd(token), text: '0' });
86
+ continue;
87
+ }
88
+ if (largest && !positionsComparable(current, largest, conic)) {
89
+ largest = undefined;
90
+ continue;
91
+ }
92
+ if (largest && largest.number >= 0 && largest.number >= current.number) {
93
+ edits.set(index, { start: token[2], end: tokenEnd(token), text: '0' });
94
+ } else {
95
+ largest = current;
96
+ }
97
+ }
98
+ return largest;
99
+ }
package/types/index.d.ts CHANGED
@@ -1,8 +1,3 @@
1
- export type SourceEdit = {
2
- start: number;
3
- end: number;
4
- text: string;
5
- };
6
1
  export type ColorStop = {
7
2
  /**
8
3
  * Comma-separated argument the stop came from.
@@ -17,10 +12,6 @@ export type ColorStop = {
17
12
  */
18
13
  position: readonly number[];
19
14
  };
20
- export type PositionValue = {
21
- number: number;
22
- unit: string;
23
- };
24
15
  /** @return {import('postcss').Plugin} */
25
16
  declare function pluginCreator(): import('postcss').Plugin;
26
17
  declare namespace pluginCreator {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AA2DI,YAAsD,UAAU,GAAtD;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAY;AAKjE,YAAkB,SAAS,GAC3B;;;;IAAmB,QAAQ,EAAhB,MAAM,CACjB;;;;IAAmB,QAAQ,EAAhB,MAAM,CACjB;;;;IAA8B,QAAQ,EAA3B,SAAS,MAAM,EAAE,CAC9B;CAAA,CAAA;AAIE,YAA0C,aAAa,GAA7C;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAe;AAkU1D,yCAAyC;AACzC,iBAAS,aAAa,IADT,OAAO,SAAS,EAAE,MAAM,CAQpC;kBAPQ,aAAa;iBAQX,IAAI;;AAEf,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,aAAa,IAAI,gBAAgB,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AA0DG,YAAkB,SAAS,GAC3B;;;;IAAmB,QAAQ,EAAhB,MAAM,CACjB;;;;IAAmB,QAAQ,EAAhB,MAAM,CACjB;;;;IAA8B,QAAQ,EAA3B,SAAS,MAAM,EAAE,CAC9B;CAAA,CAAA;AA6PD,yCAAyC;AACzC,iBAAS,aAAa,IADT,OAAO,SAAS,EAAE,MAAM,CAQpC;kBAPQ,aAAa;iBAQX,IAAI;;AAEf,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,aAAa,IAAI,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,45 @@
1
+ import type { CSSToken } from '@csstools/css-tokenizer';
2
+ export type SourceEdit = {
3
+ start: number;
4
+ end: number;
5
+ text: string;
6
+ };
7
+ export type PositionValue = {
8
+ number: number;
9
+ unit: string;
10
+ };
11
+ /**
12
+ * A zero in the position's own dimension and a zero percentage name the same
13
+ * gradient position, which is also what the unitless zero this plugin emits
14
+ * denotes. Conic positions are angles and the other gradients' are lengths; a
15
+ * zero of the other dimension is invalid there, so it must not be rewritten
16
+ * into a valid one.
17
+ *
18
+ * @param {string} unit
19
+ * @param {boolean} conic Whether the positions are `<angle-percentage>`.
20
+ * @return {boolean}
21
+ */
22
+ export declare function isPositionZeroUnit(unit: string, conic: boolean): boolean;
23
+ /**
24
+ * Whether two positions are on the same scale, so their numbers can be ordered.
25
+ * Only a length and a percentage naming zero are interchangeable.
26
+ *
27
+ * @param {PositionValue} current
28
+ * @param {PositionValue} largest
29
+ * @param {boolean} conic
30
+ * @return {boolean}
31
+ */
32
+ export declare function positionsComparable(current: PositionValue, largest: PositionValue, conic: boolean): boolean;
33
+ /**
34
+ * Colour stop fixup raises a position to the largest position before it, so a
35
+ * position at or below that non-negative maximum can be written as a zero.
36
+ *
37
+ * @param {readonly CSSToken[]} input
38
+ * @param {readonly number[]} position Offsets of the position tokens.
39
+ * @param {PositionValue | undefined} maximum Running maximum of the preceding positions.
40
+ * @param {Map<number, SourceEdit>} edits Collects the positions reduced to a zero.
41
+ * @param {boolean} conic
42
+ * @return {PositionValue | undefined} The maximum the following stops clamp to.
43
+ */
44
+ export declare function zeroPositionEdits(input: readonly CSSToken[], position: readonly number[], maximum: PositionValue | undefined, edits: Map<number, SourceEdit>, conic: boolean): PositionValue | undefined;
45
+ //# sourceMappingURL=positions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"positions.d.ts","sourceRoot":"","sources":["../../src/lib/positions.js"],"names":[],"mappings":"AAAI,OAAQ,KAAA,EAAC,QAAQ,EAAC,MAAM,yBAAyB,CAAA;AAKjD,YAAsD,UAAU,GAAtD;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAY;AAKjE,YAA0C,aAAa,GAA7C;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAe;AAK1D;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAJ5B,MAI4B,EAAE,KAAK,EAHnC,OAGmC,GAFlC,OAAO,CASlB;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EALhC,aAKgC,EAAE,OAAO,EAJzC,aAIyC,EAAE,KAAK,EAHhD,OAGgD,GAF/C,OAAO,CAWlB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAP5B,SAAS,QAAQ,EAOW,EAAE,QAAQ,EANtC,SAAS,MAAM,EAMuB,EAAE,OAAO,EAL/C,aAAa,GAAG,SAK+B,EAAE,KAAK,EAJtD,GAAG,CAAC,MAAM,EAAE,UAAU,CAIgC,EAAE,KAAK,EAH7D,OAG6D,GAF5D,aAAa,GAAG,SAAS,CAkCpC"}