postcss-minify-params 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-minify-params",
3
- "version": "9.0.3",
3
+ "version": "9.0.5",
4
4
  "description": "Minify at-rule params with PostCSS",
5
5
  "exports": {
6
6
  ".": {
@@ -41,9 +41,8 @@
41
41
  },
42
42
  "homepage": "https://github.com/cssnano/cssnano",
43
43
  "dependencies": {
44
- "browserslist": "^4.29.0",
45
- "cssnano-utils": "^7.0.3",
46
- "postcss-value-parser": "^4.2.0"
44
+ "browserslist": "^4.29.2",
45
+ "cssnano-utils": "^8.0.1"
47
46
  },
48
47
  "engines": {
49
48
  "node": "^22.22.3 || ^24.15.0 || >=26.0"
package/src/index.js CHANGED
@@ -1,138 +1,9 @@
1
1
  import getBrowsersList from '#getBrowsersList';
2
- import valueParser from 'postcss-value-parser';
3
- import cssnanoUtils from 'cssnano-utils';
2
+ import transform from './lib/transform.js';
4
3
 
5
- const { getArguments } = cssnanoUtils;
6
4
  /** @import browserslist from 'browserslist' */
7
5
 
8
- /**
9
- * Return the greatest common divisor
10
- * of two numbers.
11
- *
12
- * @param {number} a
13
- * @param {number} b
14
- * @return {number}
15
- */
16
- function gcd(a, b) {
17
- return b ? gcd(b, a % b) : a;
18
- }
19
-
20
- /**
21
- * @param {number} a
22
- * @param {number} b
23
- * @return {[number, number]}
24
- */
25
- function aspectRatio(a, b) {
26
- const divisor = gcd(a, b);
27
-
28
- return [a / divisor, b / divisor];
29
- }
30
-
31
- /**
32
- * @param {valueParser.Node[]} args
33
- * @return {string}
34
- */
35
- function split(args) {
36
- return args.map((arg) => valueParser.stringify(arg)).join('');
37
- }
38
-
39
- /**
40
- * @param {valueParser.Node} node
41
- * @return {void}
42
- */
43
- function removeNode(node) {
44
- node.value = '';
45
- node.type = 'word';
46
- }
47
-
48
- /**
49
- * @param {unknown[]} items
50
- * @return {string}
51
- */
52
- function sortAndDedupe(items) {
53
- const a = [...new Set(items)];
54
- a.sort();
55
- return a.join();
56
- }
57
-
58
- /**
59
- * @param {boolean} legacy
60
- * @param {import('postcss').AtRule} rule
61
- * @return {void}
62
- */
63
- function transform(legacy, rule) {
64
- const ruleName = rule.name.toLowerCase();
65
-
66
- // We should re-arrange parameters only for `@media` and `@supports` at-rules
67
- if (!rule.params || !['media', 'supports'].includes(ruleName)) {
68
- return;
69
- }
70
-
71
- const params = valueParser(rule.params);
72
-
73
- params.walk((node, index) => {
74
- if (node.type === 'div') {
75
- node.before = node.after = '';
76
- } else if (node.type === 'function') {
77
- node.before = '';
78
- if (
79
- node.nodes[0] &&
80
- node.nodes[0].type === 'word' &&
81
- node.nodes[0].value.startsWith('--') &&
82
- node.nodes[2] === undefined
83
- ) {
84
- node.after = ' ';
85
- } else {
86
- node.after = '';
87
- }
88
- if (
89
- node.nodes[4] &&
90
- node.nodes[0].value.toLowerCase().indexOf('-aspect-ratio') === 3
91
- ) {
92
- const [a, b] = aspectRatio(
93
- Number(node.nodes[2].value),
94
- Number(node.nodes[4].value)
95
- );
96
-
97
- node.nodes[2].value = a.toString();
98
- node.nodes[4].value = b.toString();
99
- }
100
- } else if (node.type === 'space') {
101
- node.value = ' ';
102
- } else {
103
- const prevWord = params.nodes[index - 2];
104
-
105
- if (
106
- node.value.toLowerCase() === 'all' &&
107
- rule.name.toLowerCase() === 'media' &&
108
- !prevWord
109
- ) {
110
- const nextWord = params.nodes[index + 2];
111
-
112
- if (!legacy || nextWord) {
113
- removeNode(node);
114
- }
115
-
116
- if (nextWord && nextWord.value.toLowerCase() === 'and') {
117
- const nextSpace = params.nodes[index + 1];
118
- const secondSpace = params.nodes[index + 3];
119
-
120
- removeNode(nextWord);
121
- removeNode(nextSpace);
122
- removeNode(secondSpace);
123
- }
124
- }
125
- }
126
- }, true);
127
-
128
- rule.params = sortAndDedupe(getArguments(params).map(split));
129
-
130
- if (!rule.params.length) {
131
- rule.raws.afterName = '';
132
- }
133
- }
134
-
135
- const allBugBrowers = new Set(['ie 10', 'ie 11']);
6
+ const allBugBrowsers = new Set(['ie 10', 'ie 11']);
136
7
 
137
8
  /**
138
9
  * @typedef {{ overrideBrowserslist?: string | string[] }} AutoprefixerOptions
@@ -155,13 +26,10 @@ function pluginCreator(options = {}) {
155
26
  prepare(result) {
156
27
  const { stats, env, from, file } = result.opts || {};
157
28
  const browsers = getBrowsersList(options, stats, from, file, env);
158
-
159
- const hasAllBug = !new Set(browsers).isDisjointFrom(allBugBrowers);
29
+ const hasAllBug = !new Set(browsers).isDisjointFrom(allBugBrowsers);
160
30
 
161
31
  return {
162
- /**
163
- * @param {import('postcss').Root} css
164
- */
32
+ /** @param {import('postcss').Root} css */
165
33
  OnceExit(css) {
166
34
  css.walkAtRules((rule) => transform(hasAllBug, rule));
167
35
  },
@@ -0,0 +1,86 @@
1
+ import {
2
+ decoded,
3
+ significantIndex,
4
+ TokenType,
5
+ tokenEnd,
6
+ } from './tokenUtils.js';
7
+
8
+ /** @import cssnanoUtils from 'cssnano-utils' */
9
+ /** @typedef {Exclude<ReturnType<typeof cssnanoUtils.balancedTokens>, undefined>['tokens']} Tokens */
10
+
11
+ /** @param {Tokens} input @param {{startIndex:number,endIndex:number}} segment @return {{endIndex:number,first:number,last:number}} */
12
+ function mediaSegmentInfo(input, { startIndex, endIndex }) {
13
+ const first = significantIndex(input, startIndex, 1);
14
+ let last = endIndex - 1;
15
+ while (
16
+ last >= first &&
17
+ (input[last][0] === TokenType.Whitespace ||
18
+ input[last][0] === TokenType.Comment)
19
+ ) {
20
+ last--;
21
+ }
22
+ return { endIndex, first, last };
23
+ }
24
+
25
+ /** @param {Tokens} input @param {{first:number,last:number}} segment @return {boolean} */
26
+ function isStandaloneAll(input, { first, last }) {
27
+ return (
28
+ first === last &&
29
+ input[first]?.[0] === TokenType.Ident &&
30
+ decoded(input[first]).toLowerCase() === 'all'
31
+ );
32
+ }
33
+
34
+ /** @param {boolean} legacy @param {Tokens} input @param {{endIndex:number,first:number}} segment @param {{start:number,end:number,text:string}[]} changes @return {void} */
35
+ function minifyMediaAllSegment(legacy, input, { endIndex, first }, changes) {
36
+ if (
37
+ input[first]?.[0] !== TokenType.Ident ||
38
+ decoded(input[first]).toLowerCase() !== 'all'
39
+ )
40
+ return;
41
+ const second = significantIndex(input, first + 1, 1);
42
+ const and =
43
+ second < endIndex &&
44
+ input[second]?.[0] === TokenType.Ident &&
45
+ decoded(input[second]).toLowerCase() === 'and';
46
+ if (legacy && !and) return;
47
+ const end = and ? second : first;
48
+ for (let i = first; i <= end; i++) {
49
+ if (
50
+ input[i][0] === TokenType.Ident ||
51
+ input[i][0] === TokenType.Whitespace
52
+ ) {
53
+ changes.push({
54
+ start: input[i][2],
55
+ end: tokenEnd(input[i]),
56
+ text: '',
57
+ });
58
+ }
59
+ }
60
+ const trailing = end + 1;
61
+ if (trailing < endIndex && input[trailing]?.[0] === TokenType.Whitespace) {
62
+ changes.push({
63
+ start: input[trailing][2],
64
+ end: tokenEnd(input[trailing]),
65
+ text: '',
66
+ });
67
+ }
68
+ }
69
+
70
+ /** @param {boolean} legacy @param {Exclude<ReturnType<typeof cssnanoUtils.balancedTokens>, undefined>} structure @param {{start:number,end:number,text:string}[]} changes @return {boolean} */
71
+ export default function minifyMediaAll(legacy, structure, changes) {
72
+ const { tokens: input } = structure;
73
+ const ranges = structure
74
+ .topLevelSegments()
75
+ .map((segment) => mediaSegmentInfo(input, segment));
76
+
77
+ if (!legacy && ranges.some((segment) => isStandaloneAll(input, segment))) {
78
+ const last = input.at(-1);
79
+ if (last) changes.push({ start: 0, end: tokenEnd(last), text: '' });
80
+ return true;
81
+ }
82
+
83
+ for (const segment of ranges)
84
+ minifyMediaAllSegment(legacy, input, segment, changes);
85
+ return false;
86
+ }
@@ -0,0 +1,180 @@
1
+ import cssnanoUtils from 'cssnano-utils';
2
+
3
+ const { TokenType } = cssnanoUtils;
4
+ const decoded = /** @type {typeof cssnanoUtils.decoded} */ (
5
+ cssnanoUtils.decoded
6
+ );
7
+ const tokenEnd = /** @type {typeof cssnanoUtils.tokenEnd} */ (
8
+ cssnanoUtils.tokenEnd
9
+ );
10
+
11
+ /** @typedef {Exclude<ReturnType<typeof cssnanoUtils.balancedTokens>, undefined>['tokens']} Tokens */
12
+
13
+ export { TokenType, decoded, tokenEnd };
14
+
15
+ /**
16
+ * Reports whether a value ends in a backslash that begins an escape
17
+ * sequence, as opposed to a backslash that is itself escaped.
18
+ * @param {string} value
19
+ * @return {boolean}
20
+ */
21
+ export function endsWithEscapingBackslash(value) {
22
+ if (!value.endsWith('\\')) return false;
23
+
24
+ let backslashes = 0;
25
+ for (let i = value.length - 1; i >= 0 && value[i] === '\\'; i--) {
26
+ backslashes++;
27
+ }
28
+ return backslashes % 2 === 1;
29
+ }
30
+
31
+ export const openingTypes = new Set([
32
+ TokenType.Function,
33
+ TokenType.OpenParen,
34
+ TokenType.OpenSquare,
35
+ TokenType.OpenCurly,
36
+ ]);
37
+ export const closingTypes = new Set([
38
+ TokenType.CloseParen,
39
+ TokenType.CloseSquare,
40
+ TokenType.CloseCurly,
41
+ ]);
42
+ export const calcProductEndTypes = new Set([
43
+ TokenType.Number,
44
+ TokenType.Percentage,
45
+ TokenType.Dimension,
46
+ TokenType.Ident,
47
+ TokenType.CloseParen,
48
+ ]);
49
+ export const calcProductStartTypes = new Set([
50
+ TokenType.Number,
51
+ TokenType.Percentage,
52
+ TokenType.Dimension,
53
+ TokenType.Ident,
54
+ TokenType.Function,
55
+ TokenType.OpenParen,
56
+ ]);
57
+ export const mathFunctions = cssnanoUtils.mathFunctions;
58
+ export const whitespaceInsensitiveFunctions = new Set(['selector']);
59
+ export const aspectRatioFeatures = new Set([
60
+ 'aspect-ratio',
61
+ 'min-aspect-ratio',
62
+ 'max-aspect-ratio',
63
+ 'device-aspect-ratio',
64
+ 'min-device-aspect-ratio',
65
+ 'max-device-aspect-ratio',
66
+ ]);
67
+
68
+ /**
69
+ * Convert a CSS number spelling to an exact rational number.
70
+ *
71
+ * @param {string} source
72
+ * @return {[bigint, bigint] | undefined}
73
+ */
74
+ function rational(source) {
75
+ const match = source.match(
76
+ /^[+]?((?:\d+(?:\.\d*)?)|(?:\.\d+))(?:[eE]([+\-]?\d+))?$/v
77
+ );
78
+ if (!match) return undefined;
79
+ const [, coefficient, exponent = '0'] = match;
80
+ const [whole, fraction = ''] = coefficient.split('.');
81
+ const digits = `${whole}${fraction}`;
82
+ let numerator = BigInt(digits);
83
+ let denominator = 10n ** BigInt(fraction.length);
84
+ if (numerator === 0n) return [0n, 1n];
85
+ const power = BigInt(exponent);
86
+ if (power > 10000n || power < -10000n) return undefined;
87
+ if (power >= 0n) numerator *= 10n ** power;
88
+ else denominator *= 10n ** -power;
89
+ return [numerator, denominator];
90
+ }
91
+
92
+ /** @param {bigint} a @param {bigint} b @return {bigint} */
93
+ function gcd(a, b) {
94
+ return b ? gcd(b, a % b) : a;
95
+ }
96
+
97
+ /**
98
+ * @param {string} left
99
+ * @param {string} right
100
+ * @return {[string, string] | undefined}
101
+ */
102
+ export function aspectRatio(left, right) {
103
+ const a = rational(left);
104
+ const b = rational(right);
105
+ if (!a || !b) return undefined;
106
+ const numerator = a[0] * b[1];
107
+ const denominator = b[0] * a[1];
108
+ if (numerator === 0n && denominator === 0n) return undefined;
109
+ const divisor = gcd(numerator, denominator);
110
+ const result = [String(numerator / divisor), String(denominator / divisor)];
111
+ return result.join('/').length < left.length + right.length + 1
112
+ ? /** @type {[string, string]} */ (result)
113
+ : undefined;
114
+ }
115
+
116
+ /** @param {Tokens} input @param {number} index @param {number} step @return {number} */
117
+ export function significantIndex(input, index, step) {
118
+ let cursor = index;
119
+ while (
120
+ input[cursor] &&
121
+ (input[cursor][0] === TokenType.Whitespace ||
122
+ input[cursor][0] === TokenType.Comment)
123
+ ) {
124
+ cursor += step;
125
+ }
126
+ return cursor;
127
+ }
128
+
129
+ /**
130
+ * @param {string} source
131
+ * @param {{start:number,end:number}[]} segments
132
+ * @param {{start:number,end:number,text:string}[]} edits
133
+ * @return {string[]}
134
+ */
135
+ export function serializeSegments(source, segments, edits) {
136
+ let segmentIndex = 0;
137
+ let previousEnd = -1;
138
+ let previousEdit;
139
+ for (const edit of edits) {
140
+ if (
141
+ previousEdit &&
142
+ edit.start === previousEdit.start &&
143
+ edit.end === previousEdit.end &&
144
+ edit.text === previousEdit.text
145
+ ) {
146
+ continue;
147
+ }
148
+ while (
149
+ segmentIndex < segments.length &&
150
+ edit.start >= segments[segmentIndex].end
151
+ ) {
152
+ segmentIndex++;
153
+ }
154
+ const segment = segments[segmentIndex];
155
+ if (
156
+ !segment ||
157
+ edit.start < segment.start ||
158
+ edit.start < previousEnd ||
159
+ edit.end < edit.start ||
160
+ edit.end > segment.end
161
+ ) {
162
+ return segments.map(({ start, end }) => source.slice(start, end));
163
+ }
164
+ previousEnd = edit.end;
165
+ previousEdit = edit;
166
+ }
167
+ let editIndex = 0;
168
+ return segments.map(({ start, end }) => {
169
+ let cursor = start;
170
+ let result = '';
171
+ while (editIndex < edits.length && edits[editIndex].start < end) {
172
+ const edit = edits[editIndex++];
173
+ if (edit.start < start || edit.start < cursor) continue;
174
+ result += source.slice(cursor, edit.start) + edit.text;
175
+ cursor = edit.end;
176
+ }
177
+ result += source.slice(cursor, end);
178
+ return result;
179
+ });
180
+ }
@@ -0,0 +1,102 @@
1
+ import cssnanoUtils from 'cssnano-utils';
2
+ import minifyMediaAll from './media.js';
3
+ import {
4
+ minifyAspectRatios,
5
+ minifyWhitespace,
6
+ parentIndexes,
7
+ } from './whitespace.js';
8
+ import {
9
+ endsWithEscapingBackslash,
10
+ serializeSegments,
11
+ tokenEnd,
12
+ TokenType,
13
+ } from './tokenUtils.js';
14
+
15
+ const { balancedTokens, tokens: tokenizeValue } = cssnanoUtils;
16
+
17
+ /**
18
+ * @param {boolean} legacy
19
+ * @param {import('postcss').AtRule} rule
20
+ * @return {void}
21
+ */
22
+ export default function transform(legacy, rule) {
23
+ const ruleName = rule.name.toLowerCase();
24
+
25
+ // We should re-arrange parameters only for `@media` and `@supports` at-rules
26
+ if (!rule.params || !['media', 'supports'].includes(ruleName)) return;
27
+
28
+ const source =
29
+ rule.raws.params?.value === rule.params
30
+ ? (rule.raws.params.raw ?? rule.params)
31
+ : rule.params;
32
+ const structure = balancedTokens(source);
33
+ if (!structure) {
34
+ if (rule.raws.params?.raw) {
35
+ rule.raws.params = { raw: rule.params, value: rule.params };
36
+ }
37
+ return;
38
+ }
39
+ /** @type {{start:number,end:number,text:string}[]} */
40
+ const changes = [];
41
+ const parents = parentIndexes(structure);
42
+ const mediaIsUnconditional =
43
+ ruleName === 'media' && minifyMediaAll(legacy, structure, changes);
44
+ if (mediaIsUnconditional) {
45
+ rule.params = '';
46
+ if (rule.raws.params?.raw) rule.raws.params = { raw: '', value: '' };
47
+ updateAfterName(rule);
48
+ return;
49
+ }
50
+ minifyWhitespace(structure, parents, changes);
51
+ minifyAspectRatios(ruleName === 'supports', structure, parents, changes);
52
+ const { tokens: input } = structure;
53
+ const segmentRanges = structure
54
+ .topLevelSegments()
55
+ .map(({ startIndex, endIndex }) => {
56
+ const start = input[startIndex]?.[2] ?? source.length;
57
+ const end = endIndex > startIndex ? tokenEnd(input[endIndex - 1]) : start;
58
+ return { start, end };
59
+ });
60
+ changes.sort((a, b) => a.start - b.start);
61
+ const segments = serializeSegments(source, segmentRanges, changes);
62
+ rule.params = [...new Set(segments)].toSorted().join();
63
+ if (rule.raws.params?.raw) {
64
+ rule.raws.params = { raw: rule.params, value: rule.params };
65
+ }
66
+
67
+ updateAfterName(rule);
68
+ }
69
+
70
+ /**
71
+ * Strip whitespace after @media or @supports when parameters begin with (.
72
+ *
73
+ * @param {import('postcss').AtRule} rule
74
+ * @return {void}
75
+ */
76
+ function updateAfterName(rule) {
77
+ if (!rule.params.length) {
78
+ rule.raws.afterName = '';
79
+ } else if (
80
+ rule.params.startsWith('(') &&
81
+ !endsWithEscapingBackslash(rule.name)
82
+ ) {
83
+ rule.raws.afterName = stripWhitespacePreservingComments(
84
+ rule.raws.afterName
85
+ );
86
+ }
87
+ }
88
+
89
+ /**
90
+ * @param {string | undefined} raw
91
+ * @return {string}
92
+ */
93
+ function stripWhitespacePreservingComments(raw) {
94
+ if (!raw?.includes('/*')) return '';
95
+ let result = '';
96
+ for (const [type, tokenRaw] of tokenizeValue(raw)) {
97
+ if (type !== TokenType.Whitespace) {
98
+ result += tokenRaw;
99
+ }
100
+ }
101
+ return result;
102
+ }
@@ -0,0 +1,229 @@
1
+ import cssnanoUtils from 'cssnano-utils';
2
+ import {
3
+ aspectRatio,
4
+ aspectRatioFeatures,
5
+ calcProductEndTypes,
6
+ calcProductStartTypes,
7
+ closingTypes,
8
+ decoded,
9
+ mathFunctions,
10
+ openingTypes,
11
+ significantIndex,
12
+ TokenType,
13
+ tokenEnd,
14
+ whitespaceInsensitiveFunctions,
15
+ } from './tokenUtils.js';
16
+
17
+ const { numeric } = cssnanoUtils;
18
+ /** @typedef {Exclude<ReturnType<typeof cssnanoUtils.balancedTokens>, undefined>['tokens']} Tokens */
19
+
20
+ /** @param {ReturnType<typeof cssnanoUtils.balancedTokens>} structure @return {(number|undefined)[]} */
21
+ export function parentIndexes(structure) {
22
+ if (!structure) return [];
23
+ const parents = [];
24
+ const stack = [];
25
+ for (let i = 0; i < structure.tokens.length; i++) {
26
+ const type = structure.tokens[i][0];
27
+ parents[i] = stack.at(-1);
28
+ if (openingTypes.has(type)) stack.push(i);
29
+ else if (closingTypes.has(type)) stack.pop();
30
+ }
31
+ return parents;
32
+ }
33
+
34
+ /** @param {Tokens} input @param {number} index @return {boolean} */
35
+ function isTightWhitespace(input, index) {
36
+ const previous = input[index - 1];
37
+ const next = input[index + 1];
38
+ return [
39
+ previous?.[0] === TokenType.Function,
40
+ previous?.[0] === TokenType.OpenParen,
41
+ next?.[0] === TokenType.CloseParen,
42
+ previous?.[0] === TokenType.Comma,
43
+ next?.[0] === TokenType.Comma,
44
+ previous?.[0] === TokenType.Colon,
45
+ next?.[0] === TokenType.Colon,
46
+ previous?.[0] === TokenType.Delim,
47
+ next?.[0] === TokenType.Delim,
48
+ ].some(Boolean);
49
+ }
50
+
51
+ /** @param {unknown} token @return {boolean} */
52
+ function isAdditiveOperator(token) {
53
+ return (
54
+ Array.isArray(token) &&
55
+ token[0] === TokenType.Delim &&
56
+ (token[1] === '+' || token[1] === '-')
57
+ );
58
+ }
59
+
60
+ /** @param {Tokens} input @param {number} index @param {(number|undefined)[]} parents @return {boolean} */
61
+ function isRequiredMathOperatorWhitespace(input, index, parents) {
62
+ const previous = significantIndex(input, index - 1, -1);
63
+ const next = significantIndex(input, index + 1, 1);
64
+ const before = input[previous];
65
+ const after = input[next];
66
+ let operator;
67
+ if (isAdditiveOperator(before)) operator = previous;
68
+ else if (isAdditiveOperator(after)) operator = next;
69
+ if (operator === undefined) return false;
70
+
71
+ let parent = parents[index];
72
+ while (parent !== undefined) {
73
+ const token = input[parent];
74
+ if (token?.[0] === TokenType.Function) {
75
+ const name = String(decoded(token)).toLowerCase();
76
+ if (whitespaceInsensitiveFunctions.has(name)) return false;
77
+ if (!mathFunctions.has(name)) return true;
78
+ break;
79
+ }
80
+ parent = parents[parent];
81
+ }
82
+
83
+ const left = input[significantIndex(input, operator - 1, -1)];
84
+ const right = input[significantIndex(input, operator + 1, 1)];
85
+ return (
86
+ calcProductEndTypes.has(left?.[0]) && calcProductStartTypes.has(right?.[0])
87
+ );
88
+ }
89
+
90
+ /** @param {Tokens} input @param {number} name @param {number} colon @param {number} left @param {number} slash @param {number} right @param {number} after @param {number} close @return {boolean} */
91
+ function isAspectRatioFeature(
92
+ input,
93
+ name,
94
+ colon,
95
+ left,
96
+ slash,
97
+ right,
98
+ after,
99
+ close
100
+ ) {
101
+ if (name >= close || input[name]?.[0] !== TokenType.Ident) return false;
102
+ const a = input[left] ? numeric(input[left]) : false;
103
+ const b = input[right] ? numeric(input[right]) : false;
104
+ const lowerName = String(decoded(input[name])).toLowerCase();
105
+ return (
106
+ aspectRatioFeatures.has(lowerName) &&
107
+ input[colon]?.[0] === TokenType.Colon &&
108
+ input[slash]?.[0] === TokenType.Delim &&
109
+ input[slash][1] === '/' &&
110
+ input[left]?.[0] === TokenType.Number &&
111
+ input[right]?.[0] === TokenType.Number &&
112
+ a &&
113
+ b &&
114
+ !a.unit &&
115
+ !b.unit &&
116
+ after === close
117
+ );
118
+ }
119
+
120
+ /** @param {Exclude<ReturnType<typeof cssnanoUtils.balancedTokens>, undefined>} structure @param {number} index @param {(number|undefined)[]} parents @return {{start:number,end:number,text:string}} */
121
+ function whitespaceEdit(structure, index, parents) {
122
+ const input = structure.tokens;
123
+ const token = input[index];
124
+ const next = input[index + 1];
125
+ const tight = isTightWhitespace(input, index);
126
+ const open = parents[index];
127
+ const first = open === undefined ? -1 : significantIndex(input, open + 1, 1);
128
+ const colon = significantIndex(input, first + 1, 1);
129
+ const close = open === undefined ? -1 : (structure.endForOpening(open) ?? -1);
130
+ const emptyCustomProperty =
131
+ next?.[0] === TokenType.CloseParen &&
132
+ first >= 0 &&
133
+ input[first][0] === TokenType.Ident &&
134
+ decoded(input[first]).startsWith('--') &&
135
+ input[colon]?.[0] === TokenType.Colon &&
136
+ significantIndex(input, colon + 1, 1) === close;
137
+ return {
138
+ start: token[2],
139
+ end: tokenEnd(token),
140
+ text:
141
+ tight &&
142
+ !emptyCustomProperty &&
143
+ !isRequiredMathOperatorWhitespace(input, index, parents)
144
+ ? ''
145
+ : ' ',
146
+ };
147
+ }
148
+
149
+ /** @param {Exclude<ReturnType<typeof cssnanoUtils.balancedTokens>, undefined>} structure @param {(number|undefined)[]} parents @param {{start:number,end:number,text:string}[]} changes @return {void} */
150
+ export function minifyWhitespace(structure, parents, changes) {
151
+ const { tokens: input } = structure;
152
+ const existingRanges = new Set(
153
+ changes.map((change) => `${change.start}:${change.end}`)
154
+ );
155
+ for (let i = 0; i < input.length; i++) {
156
+ if (input[i][0] === TokenType.Whitespace) {
157
+ const edit = whitespaceEdit(structure, i, parents);
158
+ const range = `${edit.start}:${edit.end}`;
159
+ if (!existingRanges.has(range)) {
160
+ changes.push(edit);
161
+ existingRanges.add(range);
162
+ }
163
+ }
164
+ }
165
+ }
166
+
167
+ /** @param {Tokens} input @param {(number|undefined)[]} parents @param {number} index @return {boolean} */
168
+ function isGroupingParent(input, parents, index) {
169
+ let parent = parents[index];
170
+ while (parent !== undefined) {
171
+ if (input[parent][0] !== TokenType.OpenParen) return false;
172
+ parent = parents[parent];
173
+ }
174
+ return true;
175
+ }
176
+
177
+ /** @param {boolean} supports @param {Exclude<ReturnType<typeof cssnanoUtils.balancedTokens>, undefined>} structure @param {(number|undefined)[]} parents @param {{start:number,end:number,text:string}[]} changes @return {void} */
178
+ export function minifyAspectRatios(supports, structure, parents, changes) {
179
+ const { tokens: input } = structure;
180
+ for (let open = 0; open < input.length; open++) {
181
+ if (
182
+ input[open][0] !== TokenType.OpenParen ||
183
+ (supports
184
+ ? !isGroupingParent(input, parents, open)
185
+ : parents[open] !== undefined)
186
+ )
187
+ continue;
188
+ const close = structure.endForOpening(open);
189
+ if (close === undefined) continue;
190
+ const name = significantIndex(input, open + 1, 1);
191
+ const colon = significantIndex(input, name + 1, 1);
192
+ const left = significantIndex(input, colon + 1, 1);
193
+ const slash = significantIndex(input, left + 1, 1);
194
+ const right = significantIndex(input, slash + 1, 1);
195
+ const after = significantIndex(input, right + 1, 1);
196
+ if (
197
+ !isAspectRatioFeature(
198
+ input,
199
+ name,
200
+ colon,
201
+ left,
202
+ slash,
203
+ right,
204
+ after,
205
+ close
206
+ )
207
+ )
208
+ continue;
209
+ const a = numeric(input[left]);
210
+ const b = numeric(input[right]);
211
+ if (
212
+ !a ||
213
+ !b ||
214
+ !Number.isFinite(a.number) ||
215
+ a.number < 0 ||
216
+ !Number.isFinite(b.number) ||
217
+ b.number < 0 ||
218
+ (a.number === 0 && b.number === 0)
219
+ )
220
+ continue;
221
+ const ratio = aspectRatio(input[left][1], input[right][1]);
222
+ if (!ratio) continue;
223
+ const [x, y] = ratio;
224
+ changes.push(
225
+ { start: input[left][2], end: tokenEnd(input[left]), text: String(x) },
226
+ { start: input[right][2], end: tokenEnd(input[right]), text: String(y) }
227
+ );
228
+ }
229
+ }
package/types/index.d.ts CHANGED
@@ -24,9 +24,7 @@ declare function pluginCreator(options?: {}): {
24
24
  file?: string;
25
25
  };
26
26
  }): {
27
- /**
28
- * @param {import('postcss').Root} css
29
- */
27
+ /** @param {import('postcss').Root} css */
30
28
  OnceExit(css: import('postcss').Root): void;
31
29
  };
32
30
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AAKI,OAAQ,KAAA,YAAY,MAAM,cAAc,CAAA;AAoIzC,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,YAAqD,OAAO,GAAlD,mBAAmB,GAAG,mBAAmB,CAAS;AAH/D;;;;GAIG;AAEH;;;;GAIG;AACH,iBAAS,aAAa,CAAC,OAAO,KAAK;IAE/B,aAAa;IAEb;;OAEG;IACH,OAAO,SAFI,OAAO,SAAS,EAAE,MAAM,GAAG;QAAC,IAAI,EAAE,mBAAmB,GAAG;YAAC,IAAI,CAAC,EAAE,MAAM,CAAA;SAAC,CAAA;KAAC;QAS/E;;WAEG;QACH,QAAQ,MAFG,OAAO,SAAS,EAAE,IAAI;;EAQxC;AAGD,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;AAKzC,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,YAAqD,OAAO,GAAlD,mBAAmB,GAAG,mBAAmB,CAAS;AAH/D;;;;GAIG;AAEH;;;;GAIG;AACH,iBAAS,aAAa,CAAC,OAAO,KAAK;IAE/B,aAAa;IAEb;;OAEG;IACH,OAAO,SAFI,OAAO,SAAS,EAAE,MAAM,GAAG;QAAC,IAAI,EAAE,mBAAmB,GAAG;YAAC,IAAI,CAAC,EAAE,MAAM,CAAA;SAAC,CAAA;KAAC;QAQ/E,0CAA0C;QAC1C,QAAQ,MADI,OAAO,SAAS,EAAE,IAAI;;EAOzC;AAGD,QAAA,MAAM,aAAa,sBAAgB,CAAC;AAEpC,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,aAAa,IAAI,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type cssnanoUtils from 'cssnano-utils';
2
+ export type Tokens = Exclude<ReturnType<typeof cssnanoUtils.balancedTokens>, undefined>['tokens'];
3
+ /** @param {boolean} legacy @param {Exclude<ReturnType<typeof cssnanoUtils.balancedTokens>, undefined>} structure @param {{start:number,end:number,text:string}[]} changes @return {boolean} */
4
+ export default function minifyMediaAll(legacy: boolean, structure: Exclude<ReturnType<typeof cssnanoUtils.balancedTokens>, undefined>, changes: {
5
+ start: number;
6
+ end: number;
7
+ text: string;
8
+ }[]): boolean;
9
+ //# sourceMappingURL=media.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../src/lib/media.js"],"names":[],"mappings":"AAOI,OAAQ,KAAA,YAAY,MAAM,eAAe,CAAA;AACzC,YAAwF,MAAM,GAApF,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,cAAc,CAAC,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAQ;AA6DlG,+LAA+L;AAC/L,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,MAAM,EADjC,OACiC,EAAE,SAAS,EADpB,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,cAAc,CAAC,EAAE,SAAS,CAC7C,EAAE,OAAO,EADyD;IAAC,KAAK,EAAC,MAAM,CAAC;IAAA,GAAG,EAAC,MAAM,CAAC;IAAA,IAAI,EAAC,MAAM,CAAA;CAAC,EAC9F,GADmH,OAAO,CAgB1L"}
@@ -0,0 +1,44 @@
1
+ import cssnanoUtils from 'cssnano-utils';
2
+ declare const TokenType: typeof import("@csstools/css-tokenizer").TokenType;
3
+ declare const decoded: typeof cssnanoUtils.decoded;
4
+ declare const tokenEnd: typeof cssnanoUtils.tokenEnd;
5
+ export type Tokens = Exclude<ReturnType<typeof cssnanoUtils.balancedTokens>, undefined>['tokens'];
6
+ /** @typedef {Exclude<ReturnType<typeof cssnanoUtils.balancedTokens>, undefined>['tokens']} Tokens */
7
+ export { TokenType, decoded, tokenEnd };
8
+ /**
9
+ * Reports whether a value ends in a backslash that begins an escape
10
+ * sequence, as opposed to a backslash that is itself escaped.
11
+ * @param {string} value
12
+ * @return {boolean}
13
+ */
14
+ export declare function endsWithEscapingBackslash(value: string): boolean;
15
+ export declare const openingTypes: Set<import("@csstools/css-tokenizer").TokenType>;
16
+ export declare const closingTypes: Set<import("@csstools/css-tokenizer").TokenType>;
17
+ export declare const calcProductEndTypes: Set<import("@csstools/css-tokenizer").TokenType>;
18
+ export declare const calcProductStartTypes: Set<import("@csstools/css-tokenizer").TokenType>;
19
+ export declare const mathFunctions: Set<string>;
20
+ export declare const whitespaceInsensitiveFunctions: Set<string>;
21
+ export declare const aspectRatioFeatures: Set<string>;
22
+ /**
23
+ * @param {string} left
24
+ * @param {string} right
25
+ * @return {[string, string] | undefined}
26
+ */
27
+ export declare function aspectRatio(left: string, right: string): [string, string] | undefined;
28
+ /** @param {Tokens} input @param {number} index @param {number} step @return {number} */
29
+ export declare function significantIndex(input: Tokens, index: number, step: number): number;
30
+ /**
31
+ * @param {string} source
32
+ * @param {{start:number,end:number}[]} segments
33
+ * @param {{start:number,end:number,text:string}[]} edits
34
+ * @return {string[]}
35
+ */
36
+ export declare function serializeSegments(source: string, segments: {
37
+ start: number;
38
+ end: number;
39
+ }[], edits: {
40
+ start: number;
41
+ end: number;
42
+ text: string;
43
+ }[]): string[];
44
+ //# sourceMappingURL=token-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-utils.d.ts","sourceRoot":"","sources":["../../src/lib/token-utils.js"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAC;AAEzC,QAAA,MAAQ,SAAS,oDAAiB,CAAC;AACnC,QAAA,MAAM,OAAO,EAAc,OAAO,YAAY,CAAC,OAE9C,CAAC;AACF,QAAA,MAAM,QAAQ,EAAc,OAAO,YAAY,CAAC,QAE/C,CAAC;AAEE,YAAwF,MAAM,GAApF,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,cAAc,CAAC,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAQ;AAAlG,qGAAqG;AAErG,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AAExC;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAHpC,MAGoC,GAFnC,OAAO,CAUlB;AAED,eAAO,MAAM,YAAY,kDAKvB,CAAC;AACH,eAAO,MAAM,YAAY,kDAIvB,CAAC;AACH,eAAO,MAAM,mBAAmB,kDAM9B,CAAC;AACH,eAAO,MAAM,qBAAqB,kDAOhC,CAAC;AACH,eAAO,MAAM,aAAa,aAA6B,CAAC;AACxD,eAAO,MAAM,8BAA8B,aAAwB,CAAC;AACpE,eAAO,MAAM,mBAAmB,aAO9B,CAAC;AA+BH;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAJrB,MAIqB,EAAE,KAAK,EAH5B,MAG4B,GAF3B,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAcvC;AAED,wFAAwF;AACxF,wBAAgB,gBAAgB,CAAC,KAAK,EAD1B,MAC0B,EAAE,KAAK,EADX,MACW,EAAE,IAAI,EADK,MACL,GAD2B,MAAM,CAWnF;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAL7B,MAK6B,EAAE,QAAQ,EAJvC;IAAC,KAAK,EAAC,MAAM,CAAC;IAAA,GAAG,EAAC,MAAM,CAAA;CAAC,EAIc,EAAE,KAAK,EAH9C;IAAC,KAAK,EAAC,MAAM,CAAC;IAAA,GAAG,EAAC,MAAM,CAAC;IAAA,IAAI,EAAC,MAAM,CAAA;CAAC,EAGS,GAF7C,MAAM,EAAE,CA+CnB"}
@@ -0,0 +1,44 @@
1
+ import cssnanoUtils from 'cssnano-utils';
2
+ declare const TokenType: typeof import("@csstools/css-tokenizer").TokenType;
3
+ declare const decoded: typeof cssnanoUtils.decoded;
4
+ declare const tokenEnd: typeof cssnanoUtils.tokenEnd;
5
+ export type Tokens = Exclude<ReturnType<typeof cssnanoUtils.balancedTokens>, undefined>['tokens'];
6
+ /** @typedef {Exclude<ReturnType<typeof cssnanoUtils.balancedTokens>, undefined>['tokens']} Tokens */
7
+ export { TokenType, decoded, tokenEnd };
8
+ /**
9
+ * Reports whether a value ends in a backslash that begins an escape
10
+ * sequence, as opposed to a backslash that is itself escaped.
11
+ * @param {string} value
12
+ * @return {boolean}
13
+ */
14
+ export declare function endsWithEscapingBackslash(value: string): boolean;
15
+ export declare const openingTypes: Set<import("@csstools/css-tokenizer").TokenType>;
16
+ export declare const closingTypes: Set<import("@csstools/css-tokenizer").TokenType>;
17
+ export declare const calcProductEndTypes: Set<import("@csstools/css-tokenizer").TokenType>;
18
+ export declare const calcProductStartTypes: Set<import("@csstools/css-tokenizer").TokenType>;
19
+ export declare const mathFunctions: Set<string>;
20
+ export declare const whitespaceInsensitiveFunctions: Set<string>;
21
+ export declare const aspectRatioFeatures: Set<string>;
22
+ /**
23
+ * @param {string} left
24
+ * @param {string} right
25
+ * @return {[string, string] | undefined}
26
+ */
27
+ export declare function aspectRatio(left: string, right: string): [string, string] | undefined;
28
+ /** @param {Tokens} input @param {number} index @param {number} step @return {number} */
29
+ export declare function significantIndex(input: Tokens, index: number, step: number): number;
30
+ /**
31
+ * @param {string} source
32
+ * @param {{start:number,end:number}[]} segments
33
+ * @param {{start:number,end:number,text:string}[]} edits
34
+ * @return {string[]}
35
+ */
36
+ export declare function serializeSegments(source: string, segments: {
37
+ start: number;
38
+ end: number;
39
+ }[], edits: {
40
+ start: number;
41
+ end: number;
42
+ text: string;
43
+ }[]): string[];
44
+ //# sourceMappingURL=tokenUtils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenUtils.d.ts","sourceRoot":"","sources":["../../src/lib/tokenUtils.js"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAC;AAEzC,QAAA,MAAQ,SAAS,oDAAiB,CAAC;AACnC,QAAA,MAAM,OAAO,EAAc,OAAO,YAAY,CAAC,OAE9C,CAAC;AACF,QAAA,MAAM,QAAQ,EAAc,OAAO,YAAY,CAAC,QAE/C,CAAC;AAEE,YAAwF,MAAM,GAApF,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,cAAc,CAAC,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAQ;AAAlG,qGAAqG;AAErG,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AAExC;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAHpC,MAGoC,GAFnC,OAAO,CAUlB;AAED,eAAO,MAAM,YAAY,kDAKvB,CAAC;AACH,eAAO,MAAM,YAAY,kDAIvB,CAAC;AACH,eAAO,MAAM,mBAAmB,kDAM9B,CAAC;AACH,eAAO,MAAM,qBAAqB,kDAOhC,CAAC;AACH,eAAO,MAAM,aAAa,aAA6B,CAAC;AACxD,eAAO,MAAM,8BAA8B,aAAwB,CAAC;AACpE,eAAO,MAAM,mBAAmB,aAO9B,CAAC;AA+BH;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAJrB,MAIqB,EAAE,KAAK,EAH5B,MAG4B,GAF3B,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAcvC;AAED,wFAAwF;AACxF,wBAAgB,gBAAgB,CAAC,KAAK,EAD1B,MAC0B,EAAE,KAAK,EADX,MACW,EAAE,IAAI,EADK,MACL,GAD2B,MAAM,CAWnF;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAL7B,MAK6B,EAAE,QAAQ,EAJvC;IAAC,KAAK,EAAC,MAAM,CAAC;IAAA,GAAG,EAAC,MAAM,CAAA;CAAC,EAIc,EAAE,KAAK,EAH9C;IAAC,KAAK,EAAC,MAAM,CAAC;IAAA,GAAG,EAAC,MAAM,CAAC;IAAA,IAAI,EAAC,MAAM,CAAA;CAAC,EAGS,GAF7C,MAAM,EAAE,CA+CnB"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @param {boolean} legacy
3
+ * @param {import('postcss').AtRule} rule
4
+ * @return {void}
5
+ */
6
+ export default function transform(legacy: boolean, rule: import('postcss').AtRule): void;
7
+ //# sourceMappingURL=transform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../src/lib/transform.js"],"names":[],"mappings":"AAgBA;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,MAAM,EAJ7B,OAI6B,EAAE,IAAI,EAHnC,OAAO,SAAS,EAAE,MAGiB,GAFlC,IAAI,CAgDf"}
@@ -0,0 +1,18 @@
1
+ import cssnanoUtils from 'cssnano-utils';
2
+ export type Tokens = Exclude<ReturnType<typeof cssnanoUtils.balancedTokens>, undefined>['tokens'];
3
+ /** @typedef {Exclude<ReturnType<typeof cssnanoUtils.balancedTokens>, undefined>['tokens']} Tokens */
4
+ /** @param {ReturnType<typeof cssnanoUtils.balancedTokens>} structure @return {(number|undefined)[]} */
5
+ export declare function parentIndexes(structure: ReturnType<typeof cssnanoUtils.balancedTokens>): (number | undefined)[];
6
+ /** @param {Exclude<ReturnType<typeof cssnanoUtils.balancedTokens>, undefined>} structure @param {(number|undefined)[]} parents @param {{start:number,end:number,text:string}[]} changes @return {void} */
7
+ export declare function minifyWhitespace(structure: Exclude<ReturnType<typeof cssnanoUtils.balancedTokens>, undefined>, parents: (number | undefined)[], changes: {
8
+ start: number;
9
+ end: number;
10
+ text: string;
11
+ }[]): void;
12
+ /** @param {boolean} supports @param {Exclude<ReturnType<typeof cssnanoUtils.balancedTokens>, undefined>} structure @param {(number|undefined)[]} parents @param {{start:number,end:number,text:string}[]} changes @return {void} */
13
+ export declare function minifyAspectRatios(supports: boolean, structure: Exclude<ReturnType<typeof cssnanoUtils.balancedTokens>, undefined>, parents: (number | undefined)[], changes: {
14
+ start: number;
15
+ end: number;
16
+ text: string;
17
+ }[]): void;
18
+ //# sourceMappingURL=whitespace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whitespace.d.ts","sourceRoot":"","sources":["../../src/lib/whitespace.js"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAC;AAiBrC,YAAwF,MAAM,GAApF,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,cAAc,CAAC,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAQ;AAAlG,qGAAqG;AAErG,uGAAuG;AACvG,wBAAgB,aAAa,CAAC,SAAS,EAD3B,UAAU,CAAC,OAAO,YAAY,CAAC,cAAc,CAClB,GADwC,CAAC,MAAM,GAAC,SAAS,CAAC,EAAE,CAYlG;AAqHD,0MAA0M;AAC1M,wBAAgB,gBAAgB,CAAC,SAAS,EAD9B,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,cAAc,CAAC,EAAE,SAAS,CACnC,EAAE,OAAO,EAD+C,CAAC,MAAM,GAAC,SAAS,CAAC,EACjE,EAAE,OAAO,EAD4E;IAAC,KAAK,EAAC,MAAM,CAAC;IAAA,GAAG,EAAC,MAAM,CAAC;IAAA,IAAI,EAAC,MAAM,CAAA;CAAC,EACjH,GADsI,IAAI,CAgBrM;AAYD,oOAAoO;AACpO,wBAAgB,kBAAkB,CAAC,QAAQ,EAD/B,OAC+B,EAAE,SAAS,EADhB,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,cAAc,CAAC,EAAE,SAAS,CACjD,EAAE,OAAO,EAD6D,CAAC,MAAM,GAAC,SAAS,CAAC,EAC/E,EAAE,OAAO,EAD0F;IAAC,KAAK,EAAC,MAAM,CAAC;IAAA,GAAG,EAAC,MAAM,CAAC;IAAA,IAAI,EAAC,MAAM,CAAA;CAAC,EAC/H,GADoJ,IAAI,CAoD/N"}