postcss-convert-values 9.0.3 → 9.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-convert-values",
3
- "version": "9.0.3",
3
+ "version": "9.0.5",
4
4
  "description": "Convert values with PostCSS (e.g. ms -> s)",
5
5
  "exports": {
6
6
  ".": {
@@ -40,8 +40,8 @@
40
40
  "url": "git+https://github.com/cssnano/cssnano.git"
41
41
  },
42
42
  "dependencies": {
43
- "browserslist": "^4.29.0",
44
- "postcss-value-parser": "^4.2.0"
43
+ "browserslist": "^4.29.1",
44
+ "cssnano-utils": "^8.0.1"
45
45
  },
46
46
  "bugs": {
47
47
  "url": "https://github.com/cssnano/cssnano/issues"
@@ -57,6 +57,7 @@
57
57
  },
58
58
  "type": "module",
59
59
  "scripts": {
60
+ "fuzz": "node ./script/fuzz.js",
60
61
  "test": "node --test"
61
62
  }
62
63
  }
package/src/index.js CHANGED
@@ -1,212 +1,8 @@
1
- import valueParser from 'postcss-value-parser';
2
1
  import getBrowsersList from '#getBrowsersList';
3
- import convert from './lib/convert.js';
2
+ import transform from './lib/transform.js';
4
3
 
5
4
  /** @import browserslist from 'browserslist' */
6
-
7
- const LENGTH_UNITS = new Set([
8
- 'em',
9
- 'ex',
10
- 'ch',
11
- 'rem',
12
- 'vw',
13
- 'vh',
14
- 'vmin',
15
- 'vmax',
16
- 'cm',
17
- 'mm',
18
- 'q',
19
- 'in',
20
- 'pt',
21
- 'pc',
22
- 'px',
23
- ]);
24
-
25
- // These properties only accept percentages, so no point in trying to transform
26
- const notALength = new Set([
27
- 'descent-override',
28
- 'ascent-override',
29
- 'font-stretch',
30
- 'size-adjust',
31
- 'line-gap-override',
32
- ]);
33
-
34
- // Can't change the unit on these properties when they're 0
35
- const keepWhenZero = new Set([
36
- 'stroke-dashoffset',
37
- 'stroke-width',
38
- 'line-height',
39
- ]);
40
-
41
- // Can't remove the % on these properties when they're 0 on IE 11
42
- const keepZeroPercentOnIE11 = new Set(['max-height', 'height', 'min-width']);
43
-
44
- const keepZeroPercentAlways = new Set([
45
- 'calc',
46
- 'color-mix',
47
- 'min',
48
- 'max',
49
- 'clamp',
50
- 'hsl',
51
- 'hsla',
52
- 'hwb',
53
- 'linear',
54
- ]);
55
-
56
- const keepZeroPercentageInKeyframe = new Set([
57
- 'border-image-width',
58
- 'stroke-dasharray',
59
- ]);
60
-
61
- /**
62
- * Numbers without digits after the dot are technically invalid,
63
- * but in that case css-value-parser returns the dot as part of the unit,
64
- * so we use this to remove the dot.
65
- *
66
- * @param {string} item
67
- * @return {string}
68
- */
69
- function stripLeadingDot(item) {
70
- if (item.charCodeAt(0) === '.'.charCodeAt(0)) {
71
- return item.slice(1);
72
- } else {
73
- return item;
74
- }
75
- }
76
-
77
- /**
78
- * @param {valueParser.Node} node
79
- * @param {Options} opts
80
- * @param {boolean} keepZeroUnit
81
- * @return {void}
82
- */
83
- function parseWord(node, opts, keepZeroUnit) {
84
- const pair = valueParser.unit(node.value);
85
- if (pair) {
86
- const num = Number(pair.number);
87
- const u = stripLeadingDot(pair.unit);
88
- if (num === 0) {
89
- node.value =
90
- 0 +
91
- (keepZeroUnit || (!LENGTH_UNITS.has(u.toLowerCase()) && u !== '%')
92
- ? u
93
- : '');
94
- if (node.value === '0ms') {
95
- node.value = '0s';
96
- }
97
- } else {
98
- node.value = convert(num, u, opts);
99
-
100
- if (
101
- typeof opts.precision === 'number' &&
102
- u.toLowerCase() === 'px' &&
103
- pair.number.includes('.')
104
- ) {
105
- const precision = Math.pow(10, opts.precision);
106
- node.value =
107
- Math.round(Number.parseFloat(node.value) * precision) / precision + u;
108
- }
109
- }
110
- }
111
- }
112
-
113
- /**
114
- * @param {valueParser.WordNode} node
115
- * @return {void}
116
- */
117
- function clampOpacity(node) {
118
- const pair = valueParser.unit(node.value);
119
- if (!pair) {
120
- return;
121
- }
122
- const num = Number(pair.number);
123
- if (num > 1) {
124
- node.value = pair.unit === '%' ? num + pair.unit : 1 + pair.unit;
125
- } else if (num < 0) {
126
- node.value = 0 + pair.unit;
127
- }
128
- }
129
-
130
- /**
131
- * @param {import('postcss').Declaration} decl
132
- * @param {string[]} browsers
133
- * @return {boolean}
134
- */
135
- function shouldKeepZeroUnit(decl, browsers) {
136
- const { parent } = decl;
137
- const lowerCasedProp = decl.prop.toLowerCase();
138
-
139
- return (
140
- (decl.value.includes('%') &&
141
- keepZeroPercentOnIE11.has(lowerCasedProp) &&
142
- browsers.includes('ie 11')) ||
143
- (keepZeroPercentageInKeyframe.has(lowerCasedProp) &&
144
- parent &&
145
- parent.parent &&
146
- parent.parent.type === 'atrule' &&
147
- /** @type {import('postcss').AtRule} */
148
- (parent.parent).name.toLowerCase() === 'keyframes') ||
149
- (lowerCasedProp === 'initial-value' &&
150
- parent &&
151
- parent.type === 'atrule' &&
152
- parent.name === 'property' &&
153
- parent.nodes !== undefined &&
154
- parent.nodes.some(
155
- (node) =>
156
- node.type === 'decl' &&
157
- node.prop.toLowerCase() === 'syntax' &&
158
- (node.value === "'<percentage>'" ||
159
- node.value === '"<percentage>"' ||
160
- node.value === "'<length-percentage>'" ||
161
- node.value === '"<length-percentage>"')
162
- )) ||
163
- keepWhenZero.has(lowerCasedProp)
164
- );
165
- }
166
- /**
167
- * @param {Options} opts
168
- * @param {string[]} browsers
169
- * @param {import('postcss').Declaration} decl
170
- * @return {void}
171
- */
172
- function transform(opts, browsers, decl) {
173
- const lowerCasedProp = decl.prop.toLowerCase();
174
- if (
175
- lowerCasedProp.includes('flex') ||
176
- (lowerCasedProp.indexOf('--') === 0 && !opts.transformCustomProperties) ||
177
- notALength.has(lowerCasedProp)
178
- ) {
179
- return;
180
- }
181
-
182
- decl.value = valueParser(decl.value)
183
- .walk((node) => {
184
- const lowerCasedValue = node.value.toLowerCase();
185
-
186
- if (node.type === 'word') {
187
- parseWord(node, opts, shouldKeepZeroUnit(decl, browsers));
188
- if (
189
- lowerCasedProp === 'opacity' ||
190
- lowerCasedProp === 'shape-image-threshold'
191
- ) {
192
- clampOpacity(node);
193
- }
194
- } else if (node.type === 'function') {
195
- if (keepZeroPercentAlways.has(lowerCasedValue)) {
196
- valueParser.walk(node.nodes, (n) => {
197
- if (n.type === 'word') {
198
- parseWord(n, opts, true);
199
- }
200
- });
201
- return false;
202
- }
203
- if (lowerCasedValue === 'url') {
204
- return false;
205
- }
206
- }
207
- })
208
- .toString();
209
- }
5
+ /** @import convert from './lib/convert.js' */
210
6
 
211
7
  const plugin = 'postcss-convert-values';
212
8
 
@@ -217,28 +13,37 @@ const plugin = 'postcss-convert-values';
217
13
  * @typedef {{precision?: false | number, transformCustomProperties?: boolean} & ConvertOptions & AutoprefixerOptions & BrowserslistOptions} Options
218
14
  */
219
15
 
220
- /**
221
- * @param {Options} opts
222
- * @return {import('postcss').Plugin}
223
- */
16
+ /** @param {Options} opts @return {import('postcss').Plugin} */
224
17
  function pluginCreator(opts = { precision: false }) {
18
+ /** @type {Map<string, boolean>} */
19
+ const browserCache = new Map();
20
+
225
21
  return {
226
22
  postcssPlugin: plugin,
227
23
 
228
- /**
229
- * @param {import('postcss').Result & {opts: BrowserslistOptions & {file?: string}}} result
230
- */
24
+ /** @param {import('postcss').Result & {opts: BrowserslistOptions & {file?: string}}} result */
231
25
  prepare(result) {
232
26
  const { stats, env, from, file } = result.opts || {};
233
- const browsers = getBrowsersList(opts, stats, from, file, env);
234
-
27
+ const overrideKey = opts.overrideBrowserslist
28
+ ? JSON.stringify(opts.overrideBrowserslist)
29
+ : '';
30
+ const pathKey = opts.path ?? '';
31
+ const envKey = opts.env ?? env ?? '';
32
+ const effectiveStats = opts.stats || stats;
33
+ const statsKey = effectiveStats ? JSON.stringify(effectiveStats) : '';
34
+ const cacheKey = `${overrideKey}\0${pathKey}\0${envKey}\0${from ?? ''}\0${file ?? ''}\0${statsKey}`;
35
+ let supportsIE = browserCache.get(cacheKey);
36
+ if (supportsIE === undefined) {
37
+ const browsers = getBrowsersList(opts, stats, from, file, env);
38
+ supportsIE = browsers.some((b) => b.startsWith('ie '));
39
+ browserCache.set(cacheKey, supportsIE);
40
+ }
41
+ const cache = new Map();
235
42
  return {
236
- /**
237
- * @param {import('postcss').Root} css
238
- */
43
+ /** @param {import('postcss').Root} css */
239
44
  OnceExit(css) {
240
45
  css.walkDecls((decl) =>
241
- transform(/** @type {Options} */ (opts), browsers, decl)
46
+ transform(/** @type {Options} */ (opts), supportsIE, decl, cache)
242
47
  );
243
48
  },
244
49
  };
@@ -1,26 +1,63 @@
1
1
  const lengthConv = new Map([
2
- ['in', 96],
3
- ['px', 1],
4
- ['pt', 4 / 3],
5
- ['pc', 16],
2
+ ['in', 288],
3
+ ['px', 3],
4
+ ['pt', 4],
5
+ ['pc', 48],
6
6
  ]);
7
7
 
8
- const timeConv = new Map([
8
+ const metricConv = new Map([
9
+ ['cm', 40],
10
+ ['mm', 4],
11
+ ['q', 1],
12
+ ]);
13
+
14
+ export const timeConv = new Map([
9
15
  ['s', 1000],
10
16
  ['ms', 1],
11
17
  ]);
12
18
 
13
- const angleConv = new Map([
14
- ['turn', 360],
15
- ['deg', 1],
19
+ export const angleConv = new Map([
20
+ ['deg', 10],
21
+ ['turn', 3600],
22
+ ['grad', 9],
23
+ ]);
24
+
25
+ export const freqConv = new Map([
26
+ ['khz', 1000],
27
+ ['hz', 1],
16
28
  ]);
17
29
 
18
- /** @typedef {{time?: boolean, length?: boolean, angle?: boolean}} ConvertOptions */
30
+ /** @typedef {{time?: boolean, length?: boolean, angle?: boolean, frequency?: boolean}} ConvertOptions */
31
+
32
+ /**
33
+ * Accurately round a number to a fixed decimal precision without IEEE-754 binary
34
+ * floating point multiplication errors.
35
+ *
36
+ * @param {number} value
37
+ * @param {number} precision
38
+ * @return {number}
39
+ */
40
+ export function roundToPrecision(value, precision) {
41
+ if (
42
+ typeof precision !== 'number' ||
43
+ precision < 0 ||
44
+ !Number.isFinite(precision) ||
45
+ !Number.isFinite(value)
46
+ ) {
47
+ return value;
48
+ }
49
+ const p = Math.floor(precision);
50
+ const [mantissa, exponent = 0] = String(value).toLowerCase().split('e');
51
+ const shifted = Number(mantissa + 'e' + (Number(exponent) + p));
52
+ const rounded = (Math.sign(shifted) || 0) * Math.round(Math.abs(shifted));
53
+ return Number(rounded + 'e-' + p);
54
+ }
55
+
19
56
  /**
20
57
  * @param {number} number
21
58
  * @return {string}
22
59
  */
23
- function dropLeadingZero(number) {
60
+ export function dropLeadingZero(number) {
24
61
  const value = String(number);
25
62
 
26
63
  if (number % 1) {
@@ -35,23 +72,23 @@ function dropLeadingZero(number) {
35
72
 
36
73
  return value;
37
74
  }
75
+
38
76
  /**
39
77
  * @param {number} number
40
78
  * @param {string} originalUnit
41
- * @param {typeof lengthConv | typeof timeConv | typeof angleConv} conversions
79
+ * @param {typeof lengthConv | typeof timeConv | typeof angleConv | typeof freqConv | typeof metricConv} conversions
42
80
  * @return {string}
43
81
  */
44
- function transform(number, originalUnit, conversions) {
45
- const conversionUnits = [...conversions.keys()].filter((u) => {
46
- return originalUnit !== u;
47
- });
48
-
82
+ function findShortestConversion(number, originalUnit, conversions) {
49
83
  const base = number * /** @type {number} */ (conversions.get(originalUnit));
50
84
 
51
85
  let shortest = '';
52
- for (const u of conversionUnits) {
53
- const value =
54
- dropLeadingZero(base / /** @type {number} */ (conversions.get(u))) + u;
86
+ for (const [u, factor] of conversions) {
87
+ if (u === originalUnit) {
88
+ continue;
89
+ }
90
+ const convertedNumber = Number((base / factor).toPrecision(15));
91
+ const value = dropLeadingZero(convertedNumber) + u;
55
92
 
56
93
  if (!shortest || value.length < shortest.length) {
57
94
  shortest = value;
@@ -60,6 +97,22 @@ function transform(number, originalUnit, conversions) {
60
97
 
61
98
  return shortest;
62
99
  }
100
+
101
+ /**
102
+ * @param {number} number
103
+ * @param {string} unit
104
+ * @return {string | undefined}
105
+ */
106
+ function convertAngle(number, unit) {
107
+ if (unit === 'rad') {
108
+ return number === 0 ? '0deg' : undefined;
109
+ }
110
+ if (angleConv.has(unit)) {
111
+ return findShortestConversion(number, unit, angleConv);
112
+ }
113
+ return undefined;
114
+ }
115
+
63
116
  /**
64
117
  * @param {number} number
65
118
  * @param {string} unit
@@ -67,24 +120,34 @@ function transform(number, originalUnit, conversions) {
67
120
  * @return {string}
68
121
  */
69
122
  const convert = function (number, unit, options) {
70
- const { time, length, angle } = options;
71
- let value = dropLeadingZero(number) + (unit ? unit : '');
72
- let converted;
123
+ const { time, length, angle, frequency } = options;
73
124
  const lowerCaseUnit = unit.toLowerCase();
125
+ let converted;
74
126
  if (length !== false && lengthConv.has(lowerCaseUnit)) {
75
- converted = transform(number, lowerCaseUnit, lengthConv);
127
+ converted = findShortestConversion(number, lowerCaseUnit, lengthConv);
128
+ } else if (length !== false && metricConv.has(lowerCaseUnit)) {
129
+ converted = findShortestConversion(number, lowerCaseUnit, metricConv);
130
+ } else if (time !== false && timeConv.has(lowerCaseUnit)) {
131
+ converted = findShortestConversion(number, lowerCaseUnit, timeConv);
76
132
  }
77
133
 
78
- if (time !== false && timeConv.has(lowerCaseUnit)) {
79
- converted = transform(number, lowerCaseUnit, timeConv);
134
+ if (!converted && angle !== false) {
135
+ converted = convertAngle(number, lowerCaseUnit);
136
+ }
137
+ if (!converted && frequency !== false && freqConv.has(lowerCaseUnit)) {
138
+ converted = findShortestConversion(number, lowerCaseUnit, freqConv);
80
139
  }
81
140
 
82
- if (angle !== false && angleConv.has(lowerCaseUnit)) {
83
- converted = transform(number, lowerCaseUnit, angleConv);
141
+ const value = dropLeadingZero(number) + (unit ? unit : '');
142
+ if (!converted) {
143
+ return value;
84
144
  }
85
145
 
86
- if (converted && converted.length < value.length) {
87
- value = converted;
146
+ if (
147
+ converted.length < value.length ||
148
+ (lowerCaseUnit === 'rad' && converted.length <= value.length)
149
+ ) {
150
+ return converted;
88
151
  }
89
152
 
90
153
  return value;
@@ -0,0 +1,332 @@
1
+ import cssnanoUtils from 'cssnano-utils';
2
+ import convert, {
3
+ dropLeadingZero,
4
+ roundToPrecision,
5
+ timeConv,
6
+ angleConv,
7
+ freqConv,
8
+ } from './convert.js';
9
+
10
+ const {
11
+ TokenType,
12
+ calcSumFunctions,
13
+ closeForOpening,
14
+ decoded,
15
+ lengthUnits,
16
+ mathFunctions,
17
+ numericSource,
18
+ tokens,
19
+ } = cssnanoUtils;
20
+
21
+ /* Functions in which a zero cannot lose its unit. Every math function qualifies
22
+ * because `calc(0%)` and `calc(0)` differ in type; the rest are listed per
23
+ * grammar, e.g. conic-gradient stops take an <angle-percentage>, which no
24
+ * unitless zero represents, while linear-gradient stops take a
25
+ * <length-percentage>, which it does. */
26
+ const functionsPreservingZeroUnits = new Set([
27
+ ...mathFunctions,
28
+ ...calcSumFunctions,
29
+ 'anchor',
30
+ 'anchor-size',
31
+ 'contrast-color',
32
+ 'view',
33
+ 'color-mix',
34
+ 'hsl',
35
+ 'hsla',
36
+ 'hwb',
37
+ 'linear',
38
+ 'conic-gradient',
39
+ 'repeating-conic-gradient',
40
+ 'cross-fade',
41
+ 'rgb',
42
+ 'rgba',
43
+ 'oklch',
44
+ 'oklab',
45
+ 'lab',
46
+ 'lch',
47
+ 'color',
48
+ 'palette-mix',
49
+ ]);
50
+
51
+ const convertibleUnits = new Set([
52
+ ...timeConv.keys(),
53
+ ...angleConv.keys(),
54
+ ...freqConv.keys(),
55
+ 'rad',
56
+ ]);
57
+
58
+ /** @typedef {Parameters<typeof convert>[2]} ConvertOptions */
59
+ /** @typedef {{precision?: false | number, transformCustomProperties?: boolean} & ConvertOptions & { overrideBrowserslist?: string | string[] } & import('browserslist').Options} Options */
60
+ /** @typedef {{keepZeroPercent: boolean, keepZeroLength: boolean, clampAlpha: boolean, isAlpha: boolean, isFont: boolean}} ReplacementFlags */
61
+
62
+ /** @param {number} number @param {string} unit @param {string} raw @param {Options} opts @param {boolean} keepZeroPercent @param {boolean} keepZeroLength @param {boolean} hasDecimal @return {string} */
63
+ function parseNumber(
64
+ number,
65
+ unit,
66
+ raw,
67
+ opts,
68
+ keepZeroPercent,
69
+ keepZeroLength,
70
+ hasDecimal
71
+ ) {
72
+ if (raw.includes('\\')) return raw;
73
+ const lowerCasedUnit = unit.toLowerCase();
74
+ if (
75
+ unit !== '' &&
76
+ unit !== '%' &&
77
+ !lengthUnits.has(lowerCasedUnit) &&
78
+ !convertibleUnits.has(lowerCasedUnit)
79
+ )
80
+ return raw;
81
+ let num = number;
82
+ if (
83
+ typeof opts.precision === 'number' &&
84
+ opts.precision >= 0 &&
85
+ (hasDecimal || !Number.isInteger(num))
86
+ ) {
87
+ num = roundToPrecision(num, opts.precision);
88
+ }
89
+ if (num === 0) {
90
+ if (convertibleUnits.has(lowerCasedUnit)) {
91
+ return convert(0, unit, opts);
92
+ }
93
+ if (unit === '%') {
94
+ return keepZeroPercent ? '0%' : '0';
95
+ }
96
+ if (lengthUnits.has(lowerCasedUnit)) {
97
+ return keepZeroLength ? '0' + unit : '0';
98
+ }
99
+ return '0';
100
+ }
101
+ return convert(num, unit, opts);
102
+ }
103
+
104
+ /** @param {string} value @param {number} number @param {string} unit @param {Options} opts @param {boolean} [clamp] @return {string} */
105
+ function clampOpacity(value, number, unit, opts, clamp = true) {
106
+ if (unit === '%') {
107
+ if (clamp) {
108
+ if (number >= 100) return '1';
109
+ if (number <= 0) return '0';
110
+ }
111
+ let decimalNumber = Number((number / 100).toPrecision(15));
112
+ if (typeof opts.precision === 'number' && opts.precision >= 0) {
113
+ decimalNumber = roundToPrecision(decimalNumber, opts.precision);
114
+ }
115
+ const decimal = dropLeadingZero(decimalNumber);
116
+ return decimal.length < value.length ? decimal : value;
117
+ }
118
+ if (clamp) {
119
+ if (number > 1) return '1';
120
+ if (number < 0) return '0';
121
+ }
122
+ return value;
123
+ }
124
+
125
+ /** @param {string} str @return {string} */
126
+ export function stripVendorPrefix(str) {
127
+ if (str.charCodeAt(0) !== 45 /* '-' */) return str;
128
+ if (str.startsWith('-webkit-')) return str.slice(8);
129
+ if (str.startsWith('-moz-')) return str.slice(5);
130
+ if (str.startsWith('-ms-')) return str.slice(4);
131
+ if (str.startsWith('-o-')) return str.slice(3);
132
+ return str;
133
+ }
134
+
135
+ /**
136
+ * @param {string} replacement
137
+ * @param {string} raw
138
+ * @param {boolean} isAlpha
139
+ * @param {boolean} isConvertibleZero
140
+ * @return {boolean}
141
+ */
142
+ function shouldReplace(replacement, raw, isAlpha, isConvertibleZero) {
143
+ if (replacement === raw) return false;
144
+ if (replacement.length < raw.length) return true;
145
+ return (isAlpha || isConvertibleZero) && replacement.length <= raw.length;
146
+ }
147
+
148
+ class FrameStack {
149
+ /** @type {string[]} */
150
+ #closeStack = [];
151
+ #keepZeroPercent;
152
+ #keepZeroLength;
153
+ #keepZeroUnitsDepth = -1;
154
+ #skippedDepth = -1;
155
+
156
+ /**
157
+ * @param {boolean} keepZeroPercent
158
+ * @param {boolean} keepZeroLength
159
+ */
160
+ constructor(keepZeroPercent, keepZeroLength) {
161
+ this.#keepZeroPercent = keepZeroPercent;
162
+ this.#keepZeroLength = keepZeroLength;
163
+ }
164
+
165
+ get isTopLevel() {
166
+ return this.#closeStack.length === 0;
167
+ }
168
+
169
+ get isSkipped() {
170
+ return this.#skippedDepth !== -1;
171
+ }
172
+
173
+ get keepZeroPercent() {
174
+ return this.#keepZeroPercent || this.#keepZeroUnitsDepth !== -1;
175
+ }
176
+
177
+ get keepZeroLength() {
178
+ return this.#keepZeroLength || this.#keepZeroUnitsDepth !== -1;
179
+ }
180
+
181
+ /**
182
+ * @param {ReturnType<typeof tokens>[number]} token
183
+ * @return {boolean}
184
+ */
185
+ advance(token) {
186
+ const type = token[0];
187
+ if (type === TokenType.Function) {
188
+ const name = stripVendorPrefix(decoded(token).toLowerCase());
189
+ const keepInFunction = functionsPreservingZeroUnits.has(name);
190
+ const depth = this.#closeStack.length + 1;
191
+ this.#closeStack.push(TokenType.CloseParen);
192
+ if (keepInFunction && this.#keepZeroUnitsDepth === -1) {
193
+ this.#keepZeroUnitsDepth = depth;
194
+ }
195
+ if (name === 'url' && this.#skippedDepth === -1) {
196
+ this.#skippedDepth = depth;
197
+ }
198
+ return true;
199
+ }
200
+ if (
201
+ type === TokenType.OpenParen ||
202
+ type === TokenType.OpenSquare ||
203
+ type === TokenType.OpenCurly
204
+ ) {
205
+ const close = closeForOpening(type);
206
+ if (close !== undefined) this.#closeStack.push(close);
207
+ return true;
208
+ }
209
+ if (
210
+ type === TokenType.CloseParen ||
211
+ type === TokenType.CloseSquare ||
212
+ type === TokenType.CloseCurly
213
+ ) {
214
+ if (
215
+ this.#closeStack.length > 0 &&
216
+ this.#closeStack[this.#closeStack.length - 1] === type
217
+ ) {
218
+ const depth = this.#closeStack.length;
219
+ if (depth === this.#keepZeroUnitsDepth) this.#keepZeroUnitsDepth = -1;
220
+ if (depth === this.#skippedDepth) this.#skippedDepth = -1;
221
+ this.#closeStack.pop();
222
+ }
223
+ return true;
224
+ }
225
+ return false;
226
+ }
227
+ }
228
+
229
+ /** @typedef {{index: number, start: number, end: number, raw: string, number: number, unit: string, hasDecimal: boolean}} NumericSource */
230
+
231
+ /**
232
+ * @param {NumericSource} source
233
+ * @param {boolean} isLineHeight
234
+ * @param {FrameStack} frames
235
+ * @param {Options} opts
236
+ * @param {ReplacementFlags} flags
237
+ * @return {{start: number, end: number, text: string} | undefined}
238
+ */
239
+ function processNumericToken(source, isLineHeight, frames, opts, flags) {
240
+ const currentKeepZeroPercent = frames.keepZeroPercent || isLineHeight;
241
+ const currentKeepZeroLength = frames.keepZeroLength || isLineHeight;
242
+
243
+ const converted = parseNumber(
244
+ source.number,
245
+ source.unit,
246
+ source.raw,
247
+ opts,
248
+ currentKeepZeroPercent,
249
+ currentKeepZeroLength,
250
+ source.hasDecimal
251
+ );
252
+ const isAlpha = flags.isAlpha && frames.isTopLevel;
253
+ const replacement = isAlpha
254
+ ? clampOpacity(
255
+ converted,
256
+ source.number,
257
+ source.unit,
258
+ opts,
259
+ flags.clampAlpha
260
+ )
261
+ : converted;
262
+ const isConvertibleZero =
263
+ source.number === 0 && convertibleUnits.has(source.unit.toLowerCase());
264
+
265
+ if (shouldReplace(replacement, source.raw, isAlpha, isConvertibleZero)) {
266
+ return {
267
+ start: source.start,
268
+ end: source.end,
269
+ text: replacement,
270
+ };
271
+ }
272
+ return undefined;
273
+ }
274
+
275
+ /**
276
+ * @param {string} value
277
+ * @param {ReplacementFlags} flags
278
+ * @param {Options} opts
279
+ * @return {{start: number, end: number, text: string}[]}
280
+ */
281
+ export function findReplacements(value, flags, opts) {
282
+ /** @type {{start: number, end: number, text: string}[]} */
283
+ const replacements = [];
284
+ const frames = new FrameStack(flags.keepZeroPercent, flags.keepZeroLength);
285
+ let afterFontSlash = false;
286
+
287
+ const input = tokens(value, { unicodeRangesAllowed: true });
288
+ for (let index = 0; index < input.length; index++) {
289
+ const token = input[index];
290
+ if (frames.advance(token)) {
291
+ if (frames.isTopLevel && afterFontSlash) afterFontSlash = false;
292
+ continue;
293
+ }
294
+ if (frames.isSkipped) continue;
295
+
296
+ const type = token[0];
297
+ if (frames.isTopLevel) {
298
+ if (type === TokenType.Whitespace || type === TokenType.Comment) {
299
+ continue;
300
+ }
301
+ if (flags.isFont && type === TokenType.Delim && token[1] === '/') {
302
+ afterFontSlash = true;
303
+ continue;
304
+ }
305
+ }
306
+
307
+ const wasAfterFontSlash = frames.isTopLevel && afterFontSlash;
308
+ if (afterFontSlash) afterFontSlash = false;
309
+
310
+ if (
311
+ type !== TokenType.Number &&
312
+ type !== TokenType.Dimension &&
313
+ type !== TokenType.Percentage
314
+ ) {
315
+ continue;
316
+ }
317
+
318
+ const source = numericSource(input, index);
319
+ if (!source) continue;
320
+ index = source.index;
321
+
322
+ const edit = processNumericToken(
323
+ source,
324
+ wasAfterFontSlash,
325
+ frames,
326
+ opts,
327
+ flags
328
+ );
329
+ if (edit) replacements.push(edit);
330
+ }
331
+ return replacements;
332
+ }
@@ -0,0 +1,234 @@
1
+ import cssnanoUtils from 'cssnano-utils';
2
+
3
+ const { TokenType, decoded, tokens } = cssnanoUtils;
4
+
5
+ const percentageSyntaxTypes = new Set([
6
+ 'percentage',
7
+ 'length-percentage',
8
+ 'angle-percentage',
9
+ 'time-percentage',
10
+ 'frequency-percentage',
11
+ ]);
12
+
13
+ /**
14
+ * @param {ReturnType<typeof tokens>} tokensList
15
+ * @param {number} i
16
+ * @return {number}
17
+ */
18
+ function skipTrivia(tokensList, i) {
19
+ let index = i;
20
+ while (index < tokensList.length) {
21
+ const type = tokensList[index][0];
22
+ if (type !== TokenType.Whitespace && type !== TokenType.Comment) {
23
+ break;
24
+ }
25
+ index++;
26
+ }
27
+ return index;
28
+ }
29
+
30
+ /**
31
+ * @param {ReturnType<typeof tokens>} tokensList
32
+ * @param {number} startIndex
33
+ * @param {string} openType
34
+ * @param {string} closeType
35
+ * @return {number}
36
+ */
37
+ function skipMatching(tokensList, startIndex, openType, closeType) {
38
+ let depth = 1;
39
+ let index = startIndex + 1;
40
+ while (index < tokensList.length) {
41
+ const type = tokensList[index][0];
42
+ if (type === openType) {
43
+ depth++;
44
+ } else if (type === closeType) {
45
+ depth--;
46
+ if (depth === 0) return index + 1;
47
+ }
48
+ index++;
49
+ }
50
+ return -1;
51
+ }
52
+
53
+ /**
54
+ * @param {ReturnType<typeof tokens>} innerTokens
55
+ * @param {number} i
56
+ * @return {number}
57
+ */
58
+ function consumeMultipliers(innerTokens, i) {
59
+ let index = i;
60
+ while (index < innerTokens.length) {
61
+ const token = innerTokens[index];
62
+ const type = token[0];
63
+ if (
64
+ type === TokenType.Delim &&
65
+ (token[1] === '+' ||
66
+ token[1] === '#' ||
67
+ token[1] === '?' ||
68
+ token[1] === '*' ||
69
+ token[1] === '!')
70
+ ) {
71
+ index = skipTrivia(innerTokens, index + 1);
72
+ } else if (type === TokenType.OpenCurly) {
73
+ const next = skipMatching(
74
+ innerTokens,
75
+ index,
76
+ TokenType.OpenCurly,
77
+ TokenType.CloseCurly
78
+ );
79
+ if (next === -1) return -1;
80
+ index = skipTrivia(innerTokens, next);
81
+ } else {
82
+ break;
83
+ }
84
+ }
85
+ return index;
86
+ }
87
+
88
+ /**
89
+ * @param {ReturnType<typeof tokens>} innerTokens
90
+ * @param {number} i
91
+ * @return {{nextIndex: number, hasPercentage: boolean} | undefined}
92
+ */
93
+ function consumeComponent(innerTokens, i) {
94
+ const token = innerTokens[i];
95
+ if (!token) return undefined;
96
+
97
+ if (token[0] === TokenType.OpenSquare) {
98
+ const closeIndex = skipMatching(
99
+ innerTokens,
100
+ i,
101
+ TokenType.OpenSquare,
102
+ TokenType.CloseSquare
103
+ );
104
+ if (closeIndex === -1) return undefined;
105
+ const subTokens = innerTokens.slice(i + 1, closeIndex - 1);
106
+ const hasPercentage = scanSyntaxAllowsPercentage(subTokens);
107
+ let nextIndex = skipTrivia(innerTokens, closeIndex);
108
+ nextIndex = consumeMultipliers(innerTokens, nextIndex);
109
+ if (nextIndex === -1) return undefined;
110
+ return { nextIndex, hasPercentage };
111
+ }
112
+
113
+ if (token[0] === TokenType.Ident) {
114
+ let nextIndex = skipTrivia(innerTokens, i + 1);
115
+ nextIndex = consumeMultipliers(innerTokens, nextIndex);
116
+ if (nextIndex === -1) return undefined;
117
+ return { nextIndex, hasPercentage: false };
118
+ }
119
+
120
+ if (token[0] !== TokenType.Delim || token[1] !== '<') {
121
+ return undefined;
122
+ }
123
+ const identIndex = skipTrivia(innerTokens, i + 1);
124
+ const ident = innerTokens[identIndex];
125
+ if (!ident || ident[0] !== TokenType.Ident) return undefined;
126
+
127
+ const hasPercentage = percentageSyntaxTypes.has(decoded(ident).toLowerCase());
128
+ let afterIdent = skipTrivia(innerTokens, identIndex + 1);
129
+
130
+ if (
131
+ afterIdent < innerTokens.length &&
132
+ innerTokens[afterIdent][0] === TokenType.OpenSquare
133
+ ) {
134
+ afterIdent = skipMatching(
135
+ innerTokens,
136
+ afterIdent,
137
+ TokenType.OpenSquare,
138
+ TokenType.CloseSquare
139
+ );
140
+ if (afterIdent === -1) return undefined;
141
+ afterIdent = skipTrivia(innerTokens, afterIdent);
142
+ }
143
+
144
+ const close = innerTokens[afterIdent];
145
+ if (!close || close[0] !== TokenType.Delim || close[1] !== '>') {
146
+ return undefined;
147
+ }
148
+
149
+ let nextIndex = skipTrivia(innerTokens, afterIdent + 1);
150
+ nextIndex = consumeMultipliers(innerTokens, nextIndex);
151
+ if (nextIndex === -1) return undefined;
152
+ return { nextIndex, hasPercentage };
153
+ }
154
+
155
+ /**
156
+ * @param {ReturnType<typeof tokens>} innerTokens
157
+ * @param {number} i
158
+ * @return {number} next index, or -1 if invalid combinator
159
+ */
160
+ function consumeCombinator(innerTokens, i) {
161
+ const sep = innerTokens[i];
162
+ if (sep[0] === TokenType.Delim && sep[1] === '|') {
163
+ let next = skipTrivia(innerTokens, i + 1);
164
+ if (
165
+ next < innerTokens.length &&
166
+ innerTokens[next][0] === TokenType.Delim &&
167
+ innerTokens[next][1] === '|'
168
+ ) {
169
+ next = skipTrivia(innerTokens, next + 1);
170
+ }
171
+ return next >= innerTokens.length ? -1 : next;
172
+ }
173
+ if (sep[0] === TokenType.Delim && sep[1] === '&') {
174
+ let next = skipTrivia(innerTokens, i + 1);
175
+ if (
176
+ next < innerTokens.length &&
177
+ innerTokens[next][0] === TokenType.Delim &&
178
+ innerTokens[next][1] === '&'
179
+ ) {
180
+ next = skipTrivia(innerTokens, next + 1);
181
+ return next >= innerTokens.length ? -1 : next;
182
+ }
183
+ return -1;
184
+ }
185
+ return i;
186
+ }
187
+
188
+ /**
189
+ * Parses an @property syntax string per CSS Properties and Values API Level 1 and CSS Values 4/5.
190
+ *
191
+ * @param {ReturnType<typeof tokens>} innerTokens
192
+ * @return {boolean}
193
+ */
194
+ function scanSyntaxAllowsPercentage(innerTokens) {
195
+ let i = skipTrivia(innerTokens, 0);
196
+ if (i >= innerTokens.length) return true;
197
+
198
+ if (
199
+ innerTokens[i][0] === TokenType.Delim &&
200
+ innerTokens[i][1] === '*' &&
201
+ skipTrivia(innerTokens, i + 1) >= innerTokens.length
202
+ ) {
203
+ return true;
204
+ }
205
+
206
+ let allowsPercentage = false;
207
+ while (i < innerTokens.length) {
208
+ const comp = consumeComponent(innerTokens, i);
209
+ if (!comp) return true;
210
+ if (comp.hasPercentage) allowsPercentage = true;
211
+ i = comp.nextIndex;
212
+
213
+ if (i < innerTokens.length) {
214
+ const nextIndex = consumeCombinator(innerTokens, i);
215
+ if (nextIndex === -1) return true;
216
+ i = nextIndex;
217
+ }
218
+ }
219
+ return allowsPercentage;
220
+ }
221
+
222
+ /** @param {string} value @return {boolean} */
223
+ export function syntaxAllowsPercentage(value) {
224
+ const outerTokens = tokens(value);
225
+ let syntaxToken;
226
+ for (const token of outerTokens) {
227
+ if (token[0] === TokenType.Whitespace || token[0] === TokenType.Comment)
228
+ continue;
229
+ if (syntaxToken || token[0] !== TokenType.String) return true;
230
+ syntaxToken = token;
231
+ }
232
+ if (!syntaxToken) return true;
233
+ return scanSyntaxAllowsPercentage(tokens(decoded(syntaxToken)));
234
+ }
@@ -0,0 +1,212 @@
1
+ import cssnanoUtils from 'cssnano-utils';
2
+ import { syntaxAllowsPercentage } from './parseSyntax.js';
3
+ import { findReplacements, stripVendorPrefix } from './findReplacements.js';
4
+
5
+ const { applyEdits } = cssnanoUtils;
6
+
7
+ const flexBasisProperties = new Set([
8
+ 'flex',
9
+ 'flex-basis',
10
+ 'flex-basic',
11
+ 'flex-preferred-size',
12
+ ]);
13
+
14
+ const alphaProperties = new Set([
15
+ 'opacity',
16
+ 'shape-image-threshold',
17
+ 'fill-opacity',
18
+ 'stroke-opacity',
19
+ 'stop-opacity',
20
+ 'flood-opacity',
21
+ ]);
22
+
23
+ const zeroPercentRetention = new Set([
24
+ 'descent-override',
25
+ 'ascent-override',
26
+ 'font-stretch',
27
+ 'font-width',
28
+ 'size-adjust',
29
+ 'line-gap-override',
30
+ 'text-size-adjust',
31
+ 'subscript-position-override',
32
+ 'superscript-position-override',
33
+ 'subscript-size-override',
34
+ 'superscript-size-override',
35
+ 'stroke-dasharray',
36
+ 'stroke-dashoffset',
37
+ 'stroke-width',
38
+ ]);
39
+
40
+ const zeroUnitRetention = {
41
+ ie11Percent: new Set(['max-height', 'height', 'min-width']),
42
+ keyframePercent: new Set([
43
+ 'border-image-width',
44
+ 'stroke-dasharray',
45
+ 'stroke-dashoffset',
46
+ 'stroke-width',
47
+ ]),
48
+ };
49
+
50
+ /** @typedef {import('./findReplacements.js').Options} Options */
51
+
52
+ /** @type {WeakMap<import('postcss').AtRule, boolean>} */
53
+ const atPropertySyntaxCache = new WeakMap();
54
+
55
+ /** @param {import('postcss').AtRule} parent @return {boolean} */
56
+ function atPropertyAllowsPercentage(parent) {
57
+ const cached = atPropertySyntaxCache.get(parent);
58
+ if (cached !== undefined) return cached;
59
+
60
+ const syntaxDecl = /** @type {import('postcss').Declaration | undefined} */ (
61
+ parent.nodes?.find(
62
+ (node) => node.type === 'decl' && node.prop.toLowerCase() === 'syntax'
63
+ )
64
+ );
65
+ const result = syntaxDecl ? syntaxAllowsPercentage(syntaxDecl.value) : true;
66
+ atPropertySyntaxCache.set(parent, result);
67
+ return result;
68
+ }
69
+
70
+ /** @param {import('postcss').Declaration} decl @return {boolean} */
71
+ function isInsideKeyframes(decl) {
72
+ let current = decl.parent;
73
+ while (current && current.type !== 'root') {
74
+ if (
75
+ current.type === 'atrule' &&
76
+ /** @type {import('postcss').AtRule} */ (current).name
77
+ .toLowerCase()
78
+ .endsWith('keyframes')
79
+ ) {
80
+ return true;
81
+ }
82
+ current = current.parent;
83
+ }
84
+ return false;
85
+ }
86
+
87
+ /** @param {string} property @param {Options} opts @return {boolean} */
88
+ function skipsTransformation(property, opts) {
89
+ return (
90
+ property === 'unicode-range' ||
91
+ (property.startsWith('--') && !opts.transformCustomProperties)
92
+ );
93
+ }
94
+
95
+ /**
96
+ * @param {import('postcss').Declaration} decl
97
+ * @param {string} lowerCasedProp
98
+ * @param {string} strippedProp
99
+ * @param {boolean | string[]} supportsIE
100
+ * @return {import('./findReplacements.js').ReplacementFlags & { hasParentContext: boolean }}
101
+ */
102
+ function getDeclarationFlags(decl, lowerCasedProp, strippedProp, supportsIE) {
103
+ const isLineHeight = strippedProp === 'line-height';
104
+ const isFlexBasis = flexBasisProperties.has(strippedProp);
105
+ const isAtProperty =
106
+ lowerCasedProp === 'initial-value' &&
107
+ decl.parent?.type === 'atrule' &&
108
+ /** @type {import('postcss').AtRule} */ (decl.parent).name.toLowerCase() ===
109
+ 'property';
110
+ const isAlpha = alphaProperties.has(strippedProp);
111
+ const isKeyframeProp = zeroUnitRetention.keyframePercent.has(strippedProp);
112
+ const inKeyframes = (isKeyframeProp || isAlpha) && isInsideKeyframes(decl);
113
+ const hasParentContext = lowerCasedProp === 'initial-value' || inKeyframes;
114
+
115
+ const hasIESupport =
116
+ typeof supportsIE === 'boolean'
117
+ ? supportsIE
118
+ : supportsIE.some((b) => b.startsWith('ie '));
119
+
120
+ const keepZeroPercent =
121
+ isLineHeight ||
122
+ isFlexBasis ||
123
+ zeroPercentRetention.has(strippedProp) ||
124
+ (hasIESupport && zeroUnitRetention.ie11Percent.has(strippedProp)) ||
125
+ (inKeyframes && isKeyframeProp) ||
126
+ (isAtProperty &&
127
+ atPropertyAllowsPercentage(
128
+ /** @type {import('postcss').AtRule} */ (decl.parent)
129
+ ));
130
+
131
+ const keepZeroLength =
132
+ isLineHeight ||
133
+ isFlexBasis ||
134
+ strippedProp === 'columns' ||
135
+ strippedProp === 'flex-order' ||
136
+ isAtProperty;
137
+
138
+ const clampAlpha = !inKeyframes;
139
+ const isFont = strippedProp === 'font';
140
+
141
+ return {
142
+ keepZeroPercent,
143
+ keepZeroLength,
144
+ clampAlpha,
145
+ isAlpha,
146
+ isFont,
147
+ hasParentContext,
148
+ };
149
+ }
150
+
151
+ /** @param {import('postcss').Declaration} decl @param {string} newValue @return {void} */
152
+ function updateDeclValue(decl, newValue) {
153
+ decl.value = newValue;
154
+ if (decl.raws?.value?.raw) {
155
+ decl.raws.value = { raw: newValue, value: newValue };
156
+ } else if (decl.raws?.value) {
157
+ delete decl.raws.value;
158
+ }
159
+ }
160
+
161
+ /** @param {import('postcss').Declaration} decl @return {string} */
162
+ function getRawOrDeclarationValue(decl) {
163
+ const raw = decl.raws?.value;
164
+ return raw?.raw && raw.value === decl.value ? raw.raw : decl.value;
165
+ }
166
+
167
+ /**
168
+ * @param {import('postcss').Declaration} decl
169
+ * @param {string} newValue
170
+ * @param {string} originalValue
171
+ * @return {void}
172
+ */
173
+ function syncDeclValue(decl, newValue, originalValue) {
174
+ const isStale = Boolean(
175
+ decl.raws?.value && decl.raws.value.value !== decl.value
176
+ );
177
+ if (newValue !== originalValue || isStale) {
178
+ updateDeclValue(decl, newValue);
179
+ }
180
+ }
181
+
182
+ /** @param {Options} opts @param {boolean | string[]} supportsIE @param {import('postcss').Declaration} decl @param {Map<string, string>} [cache] @return {void} */
183
+ export default function transform(opts, supportsIE, decl, cache) {
184
+ const lowerCasedProp = decl.prop.toLowerCase();
185
+ const strippedProp = stripVendorPrefix(lowerCasedProp);
186
+ if (skipsTransformation(strippedProp, opts)) return;
187
+ const value = getRawOrDeclarationValue(decl);
188
+ if (!/[0-9]/v.test(value)) return;
189
+
190
+ const flags = getDeclarationFlags(
191
+ decl,
192
+ lowerCasedProp,
193
+ strippedProp,
194
+ supportsIE
195
+ );
196
+
197
+ const cacheKey = flags.hasParentContext
198
+ ? undefined
199
+ : `${lowerCasedProp}:${value}`;
200
+ if (cacheKey !== undefined && cache?.has(cacheKey)) {
201
+ const cached = /** @type {string} */ (cache.get(cacheKey));
202
+ syncDeclValue(decl, cached, value);
203
+ return;
204
+ }
205
+
206
+ const replacements = findReplacements(value, flags, opts);
207
+ const result = replacements.length ? applyEdits(value, replacements) : value;
208
+ if (cacheKey !== undefined) {
209
+ cache?.set(cacheKey, result);
210
+ }
211
+ syncDeclValue(decl, result, value);
212
+ }
package/types/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import convert from './lib/convert.js';
2
1
  import type browserslist from 'browserslist';
2
+ import type convert from './lib/convert.js';
3
3
  export type ConvertOptions = Parameters<typeof convert>[2];
4
4
  export type AutoprefixerOptions = {
5
5
  overrideBrowserslist?: string | string[];
@@ -15,10 +15,7 @@ export type Options = {
15
15
  * @typedef {Pick<browserslist.Options, 'stats' | 'path' | 'env'>} BrowserslistOptions
16
16
  * @typedef {{precision?: false | number, transformCustomProperties?: boolean} & ConvertOptions & AutoprefixerOptions & BrowserslistOptions} Options
17
17
  */
18
- /**
19
- * @param {Options} opts
20
- * @return {import('postcss').Plugin}
21
- */
18
+ /** @param {Options} opts @return {import('postcss').Plugin} */
22
19
  declare function pluginCreator(opts?: Options): import('postcss').Plugin;
23
20
  declare namespace pluginCreator {
24
21
  var postcss: true;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AAEA,OAAO,OAAO,MAAM,kBAAkB,CAAC;AAEnC,OAAQ,KAAA,YAAY,MAAM,cAAc,CAAA;AAiNzC,YAAyC,cAAc,GAA7C,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC,CAAgB;AACvD,YAAwD,mBAAmB,GAAjE;IAAE,oBAAoB,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CAAE,CAAqB;AAC3E,YAAgE,mBAAmB,GAAzE,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC,CAAqB;AACnF,YAA0I,OAAO,GAAvI;IAAC,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAAC,yBAAyB,CAAC,EAAE,OAAO,CAAA;CAAC,GAAG,cAAc,GAAG,mBAAmB,GAAG,mBAAmB,CAAS;AAJpJ;;;;;GAKG;AAEH;;;GAGG;AACH,iBAAS,aAAa,CAAC,IAAI,GAHhB,OAGuC,GAFtC,OAAO,SAAS,EAAE,MAAM,CAyBnC;kBAvBQ,aAAa;iBAwBX,IAAI;;AAEf,QAAA,MAAM,aAAa,sBAAgB,CAAC;AAEpC,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":"AAGI,OAAQ,KAAA,YAAY,MAAM,cAAc,CAAA;AACxC,OAAQ,KAAA,OAAO,MAAM,kBAAkB,CAAA;AAKxC,YAAyC,cAAc,GAA7C,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC,CAAgB;AACvD,YAAwD,mBAAmB,GAAjE;IAAE,oBAAoB,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CAAE,CAAqB;AAC3E,YAAgE,mBAAmB,GAAzE,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC,CAAqB;AACnF,YAA0I,OAAO,GAAvI;IAAC,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAAC,yBAAyB,CAAC,EAAE,OAAO,CAAA;CAAC,GAAG,cAAc,GAAG,mBAAmB,GAAG,mBAAmB,CAAS;AAJpJ;;;;;GAKG;AAEH,+DAA+D;AAC/D,iBAAS,aAAa,CAAC,IAAI,GADf,OACsC,GADf,OAAO,SAAS,EAAE,MAAM,CAoC1D;kBAnCQ,aAAa;iBAoCX,IAAI;;AAEf,QAAA,MAAM,aAAa,sBAAgB,CAAC;AAEpC,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,aAAa,IAAI,gBAAgB,EAAE,CAAC"}
@@ -1,8 +1,27 @@
1
+ export declare const timeConv: Map<string, number>;
2
+ export declare const angleConv: Map<string, number>;
3
+ export declare const freqConv: Map<string, number>;
1
4
  export type ConvertOptions = {
2
5
  time?: boolean;
3
6
  length?: boolean;
4
7
  angle?: boolean;
8
+ frequency?: boolean;
5
9
  };
10
+ /** @typedef {{time?: boolean, length?: boolean, angle?: boolean, frequency?: boolean}} ConvertOptions */
11
+ /**
12
+ * Accurately round a number to a fixed decimal precision without IEEE-754 binary
13
+ * floating point multiplication errors.
14
+ *
15
+ * @param {number} value
16
+ * @param {number} precision
17
+ * @return {number}
18
+ */
19
+ export declare function roundToPrecision(value: number, precision: number): number;
20
+ /**
21
+ * @param {number} number
22
+ * @return {string}
23
+ */
24
+ export declare function dropLeadingZero(number: number): string;
6
25
  /**
7
26
  * @param {number} number
8
27
  * @param {string} unit
@@ -1 +1 @@
1
- {"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../../src/lib/convert.js"],"names":[],"mappings":"AAiBI,YAA+D,cAAc,GAAnE;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAC,CAAgB;AA6CjF;;;;;GAKG;AACH,QAAA,MAAM,OAAO,WALF,MAAM,QACN,MAAM,WACN,cAAc,KACb,MAwBX,CAAC;eAEa,OAAO"}
1
+ {"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../../src/lib/convert.js"],"names":[],"mappings":"AAaA,eAAO,MAAM,QAAQ,qBAGnB,CAAC;AAEH,eAAO,MAAM,SAAS,qBAIpB,CAAC;AAEH,eAAO,MAAM,QAAQ,qBAGnB,CAAC;AAEC,YAAoF,cAAc,GAAxF;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAC,CAAgB;AAAtG,yGAAyG;AAEzG;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAJ3B,MAI2B,EAAE,SAAS,EAHtC,MAGsC,GAFrC,MAAM,CAgBjB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAH3B,MAG2B,GAF1B,MAAM,CAgBjB;AA0CD;;;;;GAKG;AACH,QAAA,MAAM,OAAO,WALF,MAAM,QACN,MAAM,WACN,cAAc,KACb,MAkCX,CAAC;eAEa,OAAO"}
@@ -0,0 +1,38 @@
1
+ import convert from './convert.js';
2
+ export type ConvertOptions = Parameters<typeof convert>[2];
3
+ export type Options = {
4
+ precision?: false | number;
5
+ transformCustomProperties?: boolean;
6
+ } & ConvertOptions & {
7
+ overrideBrowserslist?: string | string[];
8
+ } & import('browserslist').Options;
9
+ export type ReplacementFlags = {
10
+ keepZeroPercent: boolean;
11
+ keepZeroLength: boolean;
12
+ clampAlpha: boolean;
13
+ isAlpha: boolean;
14
+ isFont: boolean;
15
+ };
16
+ /** @param {string} str @return {string} */
17
+ export declare function stripVendorPrefix(str: string): string;
18
+ export type NumericSource = {
19
+ index: number;
20
+ start: number;
21
+ end: number;
22
+ raw: string;
23
+ number: number;
24
+ unit: string;
25
+ hasDecimal: boolean;
26
+ };
27
+ /**
28
+ * @param {string} value
29
+ * @param {ReplacementFlags} flags
30
+ * @param {Options} opts
31
+ * @return {{start: number, end: number, text: string}[]}
32
+ */
33
+ export declare function findReplacements(value: string, flags: ReplacementFlags, opts: Options): {
34
+ start: number;
35
+ end: number;
36
+ text: string;
37
+ }[];
38
+ //# sourceMappingURL=findReplacements.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findReplacements.d.ts","sourceRoot":"","sources":["../../src/lib/findReplacements.js"],"names":[],"mappings":"AACA,OAAO,OAMN,MAAM,cAAc,CAAC;AAkDlB,YAAyC,cAAc,GAA7C,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC,CAAgB;AACvD,YAA8K,OAAO,GAA3K;IAAC,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAAC,yBAAyB,CAAC,EAAE,OAAO,CAAA;CAAC,GAAG,cAAc,GAAG;IAAE,oBAAoB,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CAAE,GAAG,OAAO,cAAc,EAAE,OAAO,CAAS;AACrL,YAAuH,gBAAgB,GAA7H;IAAC,eAAe,EAAE,OAAO,CAAC;IAAC,cAAc,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAC,CAAkB;AAiE3I,2CAA2C;AAC3C,wBAAgB,iBAAiB,CAAC,GAAG,EADzB,MACyB,GADJ,MAAM,CAQtC;AAgGG,YAAuH,aAAa,GAA1H;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAC,CAAe;AA8CxI;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAL3B,MAK2B,EAAE,KAAK,EAJlC,gBAIkC,EAAE,IAAI,EAHxC,OAGwC,GAFvC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,EAAE,CAqDvD"}
@@ -0,0 +1,3 @@
1
+ /** @param {string} value @return {boolean} */
2
+ export declare function syntaxAllowsPercentage(value: string): boolean;
3
+ //# sourceMappingURL=parseSyntax.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseSyntax.d.ts","sourceRoot":"","sources":["../../src/lib/parseSyntax.js"],"names":[],"mappings":"AA6NA,8CAA8C;AAC9C,wBAAgB,sBAAsB,CAAC,KAAK,EADhC,MACgC,GADT,OAAO,CAYzC"}
@@ -0,0 +1,4 @@
1
+ export type Options = import('./findReplacements.js').Options;
2
+ /** @param {Options} opts @param {boolean | string[]} supportsIE @param {import('postcss').Declaration} decl @param {Map<string, string>} [cache] @return {void} */
3
+ export default function transform(opts: Options, supportsIE: boolean | string[], decl: import('postcss').Declaration, cache?: Map<string, string>): void;
4
+ //# sourceMappingURL=transform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../src/lib/transform.js"],"names":[],"mappings":"AAiDI,YAAmD,OAAO,GAAhD,OAAO,uBAAuB,EAAE,OAAO,CAAS;AAoI9D,mKAAmK;AACnK,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,IAAI,EAD1B,OAC0B,EAAE,UAAU,EADhB,OAAO,GAAG,MAAM,EACA,EAAE,IAAI,EADiB,OAAO,SAAS,EAAE,WACnC,EAAE,KAAK,AAD8C,CAAqC,EAA7B,GAAG,CAAC,MAAM,EAAE,MAAM,CACxE,GAD4F,IAAI,CA8B9J"}