postcss-convert-values 9.0.2 → 9.0.4

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.2",
3
+ "version": "9.0.4",
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.28.9",
44
- "postcss-value-parser": "^4.2.0"
43
+ "browserslist": "^4.29.1",
44
+ "cssnano-utils": "^8.0.0"
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,8 +1,6 @@
1
1
  import { fileURLToPath } from 'node:url';
2
2
  import browserslist from 'browserslist';
3
- import nodepath from 'node:path';
4
-
5
- const { dirname } = nodepath;
3
+ import { dirname } from 'node:path';
6
4
 
7
5
  /**
8
6
  *
@@ -1,26 +1,63 @@
1
- const lengthConv = new Map([
2
- ['in', 96],
3
- ['px', 1],
4
- ['pt', 4 / 3],
5
- ['pc', 16],
1
+ export const lengthConv = new Map([
2
+ ['in', 288],
3
+ ['px', 3],
4
+ ['pt', 4],
5
+ ['pc', 48],
6
6
  ]);
7
7
 
8
- const timeConv = new Map([
8
+ export 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,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,529 @@
1
+ import cssnanoUtils from 'cssnano-utils';
2
+ import convert, {
3
+ dropLeadingZero,
4
+ roundToPrecision,
5
+ timeConv,
6
+ angleConv,
7
+ freqConv,
8
+ } from './convert.js';
9
+ import { syntaxAllowsPercentage } from './parse-syntax.js';
10
+
11
+ const {
12
+ TokenType,
13
+ applyEdits,
14
+ calcSumFunctions,
15
+ closeForOpening,
16
+ decoded,
17
+ lengthUnits,
18
+ mathFunctions,
19
+ numericSource,
20
+ tokens,
21
+ } = cssnanoUtils;
22
+
23
+ const flexBasisProperties = new Set([
24
+ 'flex',
25
+ 'flex-basis',
26
+ 'flex-basic',
27
+ 'flex-preferred-size',
28
+ ]);
29
+ const alphaProperties = new Set([
30
+ 'opacity',
31
+ 'shape-image-threshold',
32
+ 'fill-opacity',
33
+ 'stroke-opacity',
34
+ 'stop-opacity',
35
+ 'flood-opacity',
36
+ ]);
37
+ const zeroPercentRetention = new Set([
38
+ 'descent-override',
39
+ 'ascent-override',
40
+ 'font-stretch',
41
+ 'font-width',
42
+ 'size-adjust',
43
+ 'line-gap-override',
44
+ 'text-size-adjust',
45
+ 'subscript-position-override',
46
+ 'superscript-position-override',
47
+ 'subscript-size-override',
48
+ 'superscript-size-override',
49
+ 'stroke-dasharray',
50
+ 'stroke-dashoffset',
51
+ 'stroke-width',
52
+ ]);
53
+ const zeroUnitRetention = {
54
+ ie11Percent: new Set(['max-height', 'height', 'min-width']),
55
+ keyframePercent: new Set([
56
+ 'border-image-width',
57
+ 'stroke-dasharray',
58
+ 'stroke-dashoffset',
59
+ 'stroke-width',
60
+ ]),
61
+ };
62
+ /* Functions in which a zero cannot lose its unit. Every math function qualifies
63
+ * because `calc(0%)` and `calc(0)` differ in type; the rest are listed per
64
+ * grammar, e.g. conic-gradient stops take an <angle-percentage>, which no
65
+ * unitless zero represents, while linear-gradient stops take a
66
+ * <length-percentage>, which it does. */
67
+ const functionsPreservingZeroUnits = new Set([
68
+ ...mathFunctions,
69
+ ...calcSumFunctions,
70
+ 'anchor',
71
+ 'anchor-size',
72
+ 'contrast-color',
73
+ 'view',
74
+ 'color-mix',
75
+ 'hsl',
76
+ 'hsla',
77
+ 'hwb',
78
+ 'linear',
79
+ 'conic-gradient',
80
+ 'repeating-conic-gradient',
81
+ 'cross-fade',
82
+ 'rgb',
83
+ 'rgba',
84
+ 'oklch',
85
+ 'oklab',
86
+ 'lab',
87
+ 'lch',
88
+ 'color',
89
+ 'palette-mix',
90
+ ]);
91
+
92
+ const convertibleUnits = new Set([
93
+ ...timeConv.keys(),
94
+ ...angleConv.keys(),
95
+ ...freqConv.keys(),
96
+ 'rad',
97
+ ]);
98
+
99
+ /** @typedef {Parameters<typeof convert>[2]} ConvertOptions */
100
+ /** @typedef {{precision?: false | number, transformCustomProperties?: boolean} & ConvertOptions & { overrideBrowserslist?: string | string[] } & import('browserslist').Options} Options */
101
+
102
+ /** @param {number} number @param {string} unit @param {string} raw @param {Options} opts @param {boolean} keepZeroPercent @param {boolean} keepZeroLength @param {boolean} hasDecimal @return {string} */
103
+ function parseNumber(
104
+ number,
105
+ unit,
106
+ raw,
107
+ opts,
108
+ keepZeroPercent,
109
+ keepZeroLength,
110
+ hasDecimal
111
+ ) {
112
+ if (raw.includes('\\')) return raw;
113
+ const lowerCasedUnit = unit.toLowerCase();
114
+ if (
115
+ unit !== '' &&
116
+ unit !== '%' &&
117
+ !lengthUnits.has(lowerCasedUnit) &&
118
+ !convertibleUnits.has(lowerCasedUnit)
119
+ )
120
+ return raw;
121
+ let num = number;
122
+ if (
123
+ typeof opts.precision === 'number' &&
124
+ opts.precision >= 0 &&
125
+ (hasDecimal || !Number.isInteger(num))
126
+ ) {
127
+ num = roundToPrecision(num, opts.precision);
128
+ }
129
+ if (num === 0) {
130
+ if (convertibleUnits.has(lowerCasedUnit)) {
131
+ return convert(0, unit, opts);
132
+ }
133
+ if (unit === '%') {
134
+ return keepZeroPercent ? '0%' : '0';
135
+ }
136
+ if (lengthUnits.has(lowerCasedUnit)) {
137
+ return keepZeroLength ? '0' + unit : '0';
138
+ }
139
+ return '0';
140
+ }
141
+ return convert(num, unit, opts);
142
+ }
143
+
144
+ /** @param {string} value @param {number} number @param {string} unit @param {Options} opts @param {boolean} [clamp] @return {string} */
145
+ function clampOpacity(value, number, unit, opts, clamp = true) {
146
+ if (unit === '%') {
147
+ if (clamp) {
148
+ if (number >= 100) return '1';
149
+ if (number <= 0) return '0';
150
+ }
151
+ let decimalNumber = Number((number / 100).toPrecision(15));
152
+ if (typeof opts.precision === 'number' && opts.precision >= 0) {
153
+ decimalNumber = roundToPrecision(decimalNumber, opts.precision);
154
+ }
155
+ const decimal = dropLeadingZero(decimalNumber);
156
+ return decimal.length < value.length ? decimal : value;
157
+ }
158
+ if (clamp) {
159
+ if (number > 1) return '1';
160
+ if (number < 0) return '0';
161
+ }
162
+ return value;
163
+ }
164
+
165
+ /** @param {string} str @return {string} */
166
+ function stripVendorPrefix(str) {
167
+ if (str.charCodeAt(0) !== 45 /* '-' */) return str;
168
+ if (str.startsWith('-webkit-')) return str.slice(8);
169
+ if (str.startsWith('-moz-')) return str.slice(5);
170
+ if (str.startsWith('-ms-')) return str.slice(4);
171
+ if (str.startsWith('-o-')) return str.slice(3);
172
+ return str;
173
+ }
174
+
175
+ /** @type {WeakMap<import('postcss').AtRule, boolean>} */
176
+ const atPropertySyntaxCache = new WeakMap();
177
+
178
+ /** @param {import('postcss').AtRule} parent @return {boolean} */
179
+ function atPropertyAllowsPercentage(parent) {
180
+ const cached = atPropertySyntaxCache.get(parent);
181
+ if (cached !== undefined) return cached;
182
+
183
+ const syntaxDecl = /** @type {import('postcss').Declaration | undefined} */ (
184
+ parent.nodes?.find(
185
+ (node) => node.type === 'decl' && node.prop.toLowerCase() === 'syntax'
186
+ )
187
+ );
188
+ const result = syntaxDecl ? syntaxAllowsPercentage(syntaxDecl.value) : true;
189
+ atPropertySyntaxCache.set(parent, result);
190
+ return result;
191
+ }
192
+
193
+ /** @param {import('postcss').Declaration} decl @return {boolean} */
194
+ function isInsideKeyframes(decl) {
195
+ let current = decl.parent;
196
+ while (current && current.type !== 'root') {
197
+ if (
198
+ current.type === 'atrule' &&
199
+ /** @type {import('postcss').AtRule} */ (current).name
200
+ .toLowerCase()
201
+ .endsWith('keyframes')
202
+ ) {
203
+ return true;
204
+ }
205
+ current = current.parent;
206
+ }
207
+ return false;
208
+ }
209
+
210
+ /** @param {string} property @param {Options} opts @return {boolean} */
211
+ function skipsTransformation(property, opts) {
212
+ return property.startsWith('--') && !opts.transformCustomProperties;
213
+ }
214
+
215
+ /**
216
+ * @param {import('postcss').Declaration} decl
217
+ * @param {string} lowerCasedProp
218
+ * @param {string} strippedProp
219
+ * @param {boolean | string[]} supportsIE
220
+ * @return {{keepZeroPercent: boolean, keepZeroLength: boolean, hasParentContext: boolean, clampAlpha: boolean}}
221
+ */
222
+ function getDeclarationFlags(decl, lowerCasedProp, strippedProp, supportsIE) {
223
+ const isLineHeight = strippedProp === 'line-height';
224
+ const isFlexBasis = flexBasisProperties.has(strippedProp);
225
+ const isAtProperty =
226
+ lowerCasedProp === 'initial-value' &&
227
+ decl.parent?.type === 'atrule' &&
228
+ /** @type {import('postcss').AtRule} */ (decl.parent).name.toLowerCase() ===
229
+ 'property';
230
+ const isAlphaProp = alphaProperties.has(strippedProp);
231
+ const isKeyframeProp = zeroUnitRetention.keyframePercent.has(strippedProp);
232
+ const inKeyframes =
233
+ (isKeyframeProp || isAlphaProp) && isInsideKeyframes(decl);
234
+ const hasParentContext = lowerCasedProp === 'initial-value' || inKeyframes;
235
+
236
+ const hasIESupport =
237
+ typeof supportsIE === 'boolean'
238
+ ? supportsIE
239
+ : supportsIE.some((b) => b.startsWith('ie '));
240
+
241
+ const keepZeroPercent =
242
+ isLineHeight ||
243
+ isFlexBasis ||
244
+ zeroPercentRetention.has(strippedProp) ||
245
+ (hasIESupport && zeroUnitRetention.ie11Percent.has(strippedProp)) ||
246
+ (inKeyframes && isKeyframeProp) ||
247
+ (isAtProperty &&
248
+ atPropertyAllowsPercentage(
249
+ /** @type {import('postcss').AtRule} */ (decl.parent)
250
+ ));
251
+
252
+ const keepZeroLength =
253
+ isLineHeight ||
254
+ isFlexBasis ||
255
+ strippedProp === 'columns' ||
256
+ strippedProp === 'flex-order' ||
257
+ isAtProperty;
258
+
259
+ const clampAlpha = !inKeyframes;
260
+
261
+ return { keepZeroPercent, keepZeroLength, hasParentContext, clampAlpha };
262
+ }
263
+
264
+ /**
265
+ * @param {string} replacement
266
+ * @param {string} raw
267
+ * @param {boolean} isAlpha
268
+ * @param {boolean} isConvertibleZero
269
+ * @return {boolean}
270
+ */
271
+ function shouldReplace(replacement, raw, isAlpha, isConvertibleZero) {
272
+ if (replacement === raw) return false;
273
+ if (replacement.length < raw.length) return true;
274
+ return (isAlpha || isConvertibleZero) && replacement.length <= raw.length;
275
+ }
276
+
277
+ class FrameStack {
278
+ /** @type {string[]} */
279
+ #closeStack = [];
280
+ #keepZeroPercent;
281
+ #keepZeroLength;
282
+ #keepZeroUnitsDepth = -1;
283
+ #skippedDepth = -1;
284
+
285
+ /**
286
+ * @param {boolean} keepZeroPercent
287
+ * @param {boolean} keepZeroLength
288
+ */
289
+ constructor(keepZeroPercent, keepZeroLength) {
290
+ this.#keepZeroPercent = keepZeroPercent;
291
+ this.#keepZeroLength = keepZeroLength;
292
+ }
293
+
294
+ get isTopLevel() {
295
+ return this.#closeStack.length === 0;
296
+ }
297
+
298
+ get isSkipped() {
299
+ return this.#skippedDepth !== -1;
300
+ }
301
+
302
+ get keepZeroPercent() {
303
+ return this.#keepZeroPercent || this.#keepZeroUnitsDepth !== -1;
304
+ }
305
+
306
+ get keepZeroLength() {
307
+ return this.#keepZeroLength || this.#keepZeroUnitsDepth !== -1;
308
+ }
309
+
310
+ /**
311
+ * @param {ReturnType<typeof tokens>[number]} token
312
+ * @return {boolean}
313
+ */
314
+ advance(token) {
315
+ const type = token[0];
316
+ if (type === TokenType.Function) {
317
+ const name = stripVendorPrefix(decoded(token).toLowerCase());
318
+ const keepInFunction = functionsPreservingZeroUnits.has(name);
319
+ const depth = this.#closeStack.length + 1;
320
+ this.#closeStack.push(TokenType.CloseParen);
321
+ if (keepInFunction && this.#keepZeroUnitsDepth === -1) {
322
+ this.#keepZeroUnitsDepth = depth;
323
+ }
324
+ if (name === 'url' && this.#skippedDepth === -1) {
325
+ this.#skippedDepth = depth;
326
+ }
327
+ return true;
328
+ }
329
+ if (
330
+ type === TokenType.OpenParen ||
331
+ type === TokenType.OpenSquare ||
332
+ type === TokenType.OpenCurly
333
+ ) {
334
+ const close = closeForOpening(type);
335
+ if (close !== undefined) this.#closeStack.push(close);
336
+ return true;
337
+ }
338
+ if (
339
+ type === TokenType.CloseParen ||
340
+ type === TokenType.CloseSquare ||
341
+ type === TokenType.CloseCurly
342
+ ) {
343
+ if (
344
+ this.#closeStack.length > 0 &&
345
+ this.#closeStack[this.#closeStack.length - 1] === type
346
+ ) {
347
+ const depth = this.#closeStack.length;
348
+ if (depth === this.#keepZeroUnitsDepth) this.#keepZeroUnitsDepth = -1;
349
+ if (depth === this.#skippedDepth) this.#skippedDepth = -1;
350
+ this.#closeStack.pop();
351
+ }
352
+ return true;
353
+ }
354
+ return false;
355
+ }
356
+ }
357
+
358
+ /** @typedef {{index: number, start: number, end: number, raw: string, number: number, unit: string, hasDecimal: boolean}} NumericSource */
359
+
360
+ /**
361
+ * @param {NumericSource} source
362
+ * @param {boolean} isLineHeight
363
+ * @param {string} strippedProp
364
+ * @param {FrameStack} frames
365
+ * @param {Options} opts
366
+ * @param {boolean} clampAlpha
367
+ * @return {{start: number, end: number, text: string} | undefined}
368
+ */
369
+ function processNumericToken(
370
+ source,
371
+ isLineHeight,
372
+ strippedProp,
373
+ frames,
374
+ opts,
375
+ clampAlpha
376
+ ) {
377
+ const currentKeepZeroPercent = frames.keepZeroPercent || isLineHeight;
378
+ const currentKeepZeroLength = frames.keepZeroLength || isLineHeight;
379
+
380
+ const converted = parseNumber(
381
+ source.number,
382
+ source.unit,
383
+ source.raw,
384
+ opts,
385
+ currentKeepZeroPercent,
386
+ currentKeepZeroLength,
387
+ source.hasDecimal
388
+ );
389
+ const isAlpha = alphaProperties.has(strippedProp) && frames.isTopLevel;
390
+ const replacement = isAlpha
391
+ ? clampOpacity(converted, source.number, source.unit, opts, clampAlpha)
392
+ : converted;
393
+ const isConvertibleZero =
394
+ source.number === 0 && convertibleUnits.has(source.unit.toLowerCase());
395
+
396
+ if (shouldReplace(replacement, source.raw, isAlpha, isConvertibleZero)) {
397
+ return {
398
+ start: source.start,
399
+ end: source.end,
400
+ text: replacement,
401
+ };
402
+ }
403
+ return undefined;
404
+ }
405
+
406
+ /**
407
+ * @param {string} value
408
+ * @param {string} strippedProp
409
+ * @param {{keepZeroPercent: boolean, keepZeroLength: boolean, clampAlpha: boolean}} flags
410
+ * @param {Options} opts
411
+ * @return {{start: number, end: number, text: string}[]}
412
+ */
413
+ function findReplacements(value, strippedProp, flags, opts) {
414
+ /** @type {{start: number, end: number, text: string}[]} */
415
+ const replacements = [];
416
+ const frames = new FrameStack(flags.keepZeroPercent, flags.keepZeroLength);
417
+ const isFont = strippedProp === 'font';
418
+ let afterFontSlash = false;
419
+
420
+ const input = tokens(value);
421
+ for (let index = 0; index < input.length; index++) {
422
+ const token = input[index];
423
+ if (frames.advance(token)) {
424
+ if (frames.isTopLevel && afterFontSlash) afterFontSlash = false;
425
+ continue;
426
+ }
427
+ if (frames.isSkipped) continue;
428
+
429
+ const type = token[0];
430
+ if (frames.isTopLevel) {
431
+ if (type === TokenType.Whitespace || type === TokenType.Comment) {
432
+ continue;
433
+ }
434
+ if (isFont && type === TokenType.Delim && token[1] === '/') {
435
+ afterFontSlash = true;
436
+ continue;
437
+ }
438
+ }
439
+
440
+ const wasAfterFontSlash = frames.isTopLevel && afterFontSlash;
441
+ if (afterFontSlash) afterFontSlash = false;
442
+
443
+ if (
444
+ type !== TokenType.Number &&
445
+ type !== TokenType.Dimension &&
446
+ type !== TokenType.Percentage
447
+ ) {
448
+ continue;
449
+ }
450
+
451
+ const source = numericSource(input, index);
452
+ if (!source) continue;
453
+ index = source.index;
454
+
455
+ const edit = processNumericToken(
456
+ source,
457
+ wasAfterFontSlash,
458
+ strippedProp,
459
+ frames,
460
+ opts,
461
+ flags.clampAlpha
462
+ );
463
+ if (edit) replacements.push(edit);
464
+ }
465
+ return replacements;
466
+ }
467
+
468
+ /** @param {import('postcss').Declaration} decl @param {string} newValue @return {void} */
469
+ function updateDeclValue(decl, newValue) {
470
+ decl.value = newValue;
471
+ if (decl.raws?.value?.raw) {
472
+ decl.raws.value = { raw: newValue, value: newValue };
473
+ } else if (decl.raws?.value) {
474
+ delete decl.raws.value;
475
+ }
476
+ }
477
+
478
+ /** @param {import('postcss').Declaration} decl @return {string} */
479
+ function getRawOrDeclarationValue(decl) {
480
+ const raw = decl.raws?.value;
481
+ return raw?.raw && raw.value === decl.value ? raw.raw : decl.value;
482
+ }
483
+
484
+ /**
485
+ * @param {import('postcss').Declaration} decl
486
+ * @param {string} newValue
487
+ * @param {string} originalValue
488
+ * @return {void}
489
+ */
490
+ function syncDeclValue(decl, newValue, originalValue) {
491
+ const isStale = Boolean(
492
+ decl.raws?.value && decl.raws.value.value !== decl.value
493
+ );
494
+ if (newValue !== originalValue || isStale) {
495
+ updateDeclValue(decl, newValue);
496
+ }
497
+ }
498
+
499
+ /** @param {Options} opts @param {boolean | string[]} supportsIE @param {import('postcss').Declaration} decl @param {Map<string, string>} [cache] @return {void} */
500
+ export default function transform(opts, supportsIE, decl, cache) {
501
+ const lowerCasedProp = decl.prop.toLowerCase();
502
+ if (skipsTransformation(lowerCasedProp, opts)) return;
503
+ const value = getRawOrDeclarationValue(decl);
504
+ if (!/[0-9]/v.test(value)) return;
505
+
506
+ const strippedProp = stripVendorPrefix(lowerCasedProp);
507
+ const flags = getDeclarationFlags(
508
+ decl,
509
+ lowerCasedProp,
510
+ strippedProp,
511
+ supportsIE
512
+ );
513
+
514
+ const cacheKey = flags.hasParentContext
515
+ ? undefined
516
+ : `${lowerCasedProp}:${value}`;
517
+ if (cacheKey !== undefined && cache?.has(cacheKey)) {
518
+ const cached = /** @type {string} */ (cache.get(cacheKey));
519
+ syncDeclValue(decl, cached, value);
520
+ return;
521
+ }
522
+
523
+ const replacements = findReplacements(value, strippedProp, flags, opts);
524
+ const result = replacements.length ? applyEdits(value, replacements) : value;
525
+ if (cacheKey !== undefined) {
526
+ cache?.set(cacheKey, result);
527
+ }
528
+ syncDeclValue(decl, result, value);
529
+ }
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 +1 @@
1
- {"version":3,"file":"computeBrowsersToSupport.d.ts","sourceRoot":"","sources":["../../src/lib/computeBrowsersToSupport.js"],"names":[],"mappings":"AACA,OAAO,YAAY,MAAM,cAAc,CAAC;AAKxC;;;;;;;;GAQG;AACH,iBAAS,wBAAwB,CAAC,OAAO,EAP9B;IAAC,oBAAoB,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAAC,IAAI,CAAC,EAAE,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAAC,GAAG,CAAC,EAAE,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;CAOtI,EAAE,KAAK,EANrC,YAAY,CAAC,OAAO,CAAC,OAAO,CAMS,EAAE,IAAI,EAL3C,MAAM,GAAC,SAKoC,EAAE,IAAI,AAJzD,CACA,EADQ,MAIiD,EAAE,GAAG,AAH9D,CACA,EADQ,YAAY,CAAC,OAAO,CAAC,KAAK,CAG4B,GAFpD,MAAM,EAAE,CASpB;eAEc,wBAAwB"}
1
+ {"version":3,"file":"computeBrowsersToSupport.d.ts","sourceRoot":"","sources":["../../src/lib/computeBrowsersToSupport.js"],"names":[],"mappings":"AACA,OAAO,YAAY,MAAM,cAAc,CAAC;AAGxC;;;;;;;;GAQG;AACH,iBAAS,wBAAwB,CAAC,OAAO,EAP9B;IAAC,oBAAoB,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAAC,IAAI,CAAC,EAAE,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAAC,GAAG,CAAC,EAAE,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;CAOtI,EAAE,KAAK,EANrC,YAAY,CAAC,OAAO,CAAC,OAAO,CAMS,EAAE,IAAI,EAL3C,MAAM,GAAC,SAKoC,EAAE,IAAI,AAJzD,CACA,EADQ,MAIiD,EAAE,GAAG,AAH9D,CACA,EADQ,YAAY,CAAC,OAAO,CAAC,KAAK,CAG4B,GAFpD,MAAM,EAAE,CASpB;eAEc,wBAAwB"}
@@ -1,8 +1,29 @@
1
+ export declare const lengthConv: Map<string, number>;
2
+ export declare const metricConv: Map<string, number>;
3
+ export declare const timeConv: Map<string, number>;
4
+ export declare const angleConv: Map<string, number>;
5
+ export declare const freqConv: Map<string, number>;
1
6
  export type ConvertOptions = {
2
7
  time?: boolean;
3
8
  length?: boolean;
4
9
  angle?: boolean;
10
+ frequency?: boolean;
5
11
  };
12
+ /** @typedef {{time?: boolean, length?: boolean, angle?: boolean, frequency?: boolean}} ConvertOptions */
13
+ /**
14
+ * Accurately round a number to a fixed decimal precision without IEEE-754 binary
15
+ * floating point multiplication errors.
16
+ *
17
+ * @param {number} value
18
+ * @param {number} precision
19
+ * @return {number}
20
+ */
21
+ export declare function roundToPrecision(value: number, precision: number): number;
22
+ /**
23
+ * @param {number} number
24
+ * @return {string}
25
+ */
26
+ export declare function dropLeadingZero(number: number): string;
6
27
  /**
7
28
  * @param {number} number
8
29
  * @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,UAAU,qBAKrB,CAAC;AAEH,eAAO,MAAM,UAAU,qBAIrB,CAAC;AAEH,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,3 @@
1
+ /** @param {string} value @return {boolean} */
2
+ export declare function syntaxAllowsPercentage(value: string): boolean;
3
+ //# sourceMappingURL=parse-syntax.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-syntax.d.ts","sourceRoot":"","sources":["../../src/lib/parse-syntax.js"],"names":[],"mappings":"AA6NA,8CAA8C;AAC9C,wBAAgB,sBAAsB,CAAC,KAAK,EADhC,MACgC,GADT,OAAO,CAYzC"}
@@ -0,0 +1,20 @@
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 NumericSource = {
10
+ index: number;
11
+ start: number;
12
+ end: number;
13
+ raw: string;
14
+ number: number;
15
+ unit: string;
16
+ hasDecimal: boolean;
17
+ };
18
+ /** @param {Options} opts @param {boolean | string[]} supportsIE @param {import('postcss').Declaration} decl @param {Map<string, string>} [cache] @return {void} */
19
+ export default function transform(opts: Options, supportsIE: boolean | string[], decl: import('postcss').Declaration, cache?: Map<string, string>): void;
20
+ //# sourceMappingURL=transform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../src/lib/transform.js"],"names":[],"mappings":"AACA,OAAO,OAMN,MAAM,cAAc,CAAC;AA2FlB,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;AAkQrL,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;AA6IxI,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"}