@tbela99/css-parser 0.1.0 → 0.3.0

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.
Files changed (40) hide show
  1. package/README.md +267 -2
  2. package/dist/config.json.js +611 -4
  3. package/dist/index-umd-web.js +2898 -1223
  4. package/dist/index.cjs +2898 -1223
  5. package/dist/lib/ast/expand.js +11 -11
  6. package/dist/lib/ast/features/calc.js +33 -224
  7. package/dist/lib/ast/features/index.js +3 -3
  8. package/dist/lib/ast/features/inlinecssvariables.js +46 -31
  9. package/dist/lib/ast/features/shorthand.js +7 -7
  10. package/dist/lib/ast/features/utils/math.js +95 -0
  11. package/dist/lib/ast/math/expression.js +185 -0
  12. package/dist/lib/ast/math/math.js +95 -0
  13. package/dist/lib/ast/minify.js +34 -29
  14. package/dist/lib/ast/types.js +108 -78
  15. package/dist/lib/ast/walk.js +42 -9
  16. package/dist/lib/fs/resolve.js +4 -3
  17. package/dist/lib/iterable/set.js +48 -0
  18. package/dist/lib/iterable/weakmap.js +53 -0
  19. package/dist/lib/iterable/weakset.js +48 -0
  20. package/dist/lib/parser/declaration/list.js +7 -3
  21. package/dist/lib/parser/declaration/map.js +86 -7
  22. package/dist/lib/parser/declaration/set.js +43 -23
  23. package/dist/lib/parser/parse.js +561 -387
  24. package/dist/lib/parser/tokenize.js +42 -13
  25. package/dist/lib/parser/utils/declaration.js +67 -0
  26. package/dist/lib/parser/utils/syntax.js +32 -2
  27. package/dist/lib/parser/utils/type.js +7 -2
  28. package/dist/lib/renderer/render.js +163 -47
  29. package/dist/lib/renderer/utils/calccolor.js +238 -0
  30. package/dist/lib/renderer/utils/color.js +36 -164
  31. package/dist/lib/renderer/utils/hex.js +124 -0
  32. package/dist/lib/renderer/utils/hsl.js +49 -0
  33. package/dist/lib/renderer/utils/hsv.js +15 -0
  34. package/dist/lib/renderer/utils/hwb.js +50 -0
  35. package/dist/lib/renderer/utils/rgb.js +66 -0
  36. package/dist/node/index.js +8 -12
  37. package/dist/web/index.js +8 -12
  38. package/package.json +9 -7
  39. package/dist/index.d.ts +0 -1056
  40. /package/dist/lib/ast/{utiles → utils}/minifyfeature.js +0 -0
@@ -16,7 +16,6 @@ function* tokenize(stream) {
16
16
  };
17
17
  let value;
18
18
  let buffer = '';
19
- // let input: string = '';
20
19
  function consumeWhiteSpace() {
21
20
  let count = 0;
22
21
  while (isWhiteSpace(stream.charAt(count + ind + 1).charCodeAt(0))) {
@@ -26,7 +25,7 @@ function* tokenize(stream) {
26
25
  return count;
27
26
  }
28
27
  function pushToken(token, hint) {
29
- const result = { token, hint, position: { ...position }, bytesIn: ind };
28
+ const result = { token, hint, position: { ...position }, bytesIn: ind + 1 };
30
29
  position.ind = ind;
31
30
  position.lin = lin;
32
31
  position.col = col == 0 ? 1 : col;
@@ -223,8 +222,10 @@ function* tokenize(stream) {
223
222
  // EOF
224
223
  if (!(value = next())) {
225
224
  // end of stream ignore \\
226
- yield pushToken(buffer);
227
- buffer = '';
225
+ if (buffer.length > 0) {
226
+ yield pushToken(buffer);
227
+ buffer = '';
228
+ }
228
229
  break;
229
230
  }
230
231
  buffer += prev() + value;
@@ -233,29 +234,51 @@ function* tokenize(stream) {
233
234
  case "'":
234
235
  yield* consumeString(value);
235
236
  break;
237
+ case '^':
236
238
  case '~':
237
239
  case '|':
240
+ case '$':
241
+ if (value == '|' && peek() == '|') {
242
+ next();
243
+ yield pushToken('', EnumToken.ColumnCombinatorTokenType);
244
+ buffer = '';
245
+ break;
246
+ }
238
247
  if (buffer.length > 0) {
239
248
  yield pushToken(buffer);
240
249
  buffer = '';
241
250
  }
242
251
  buffer += value;
243
- if (!(value = next())) {
252
+ if (!(value = peek())) {
244
253
  yield pushToken(buffer);
245
254
  buffer = '';
246
255
  break;
247
256
  }
248
- if (value == '=') {
249
- buffer += value;
250
- yield pushToken(buffer, buffer[0] == '~' ? EnumToken.IncludesTokenType : EnumToken.DashMatchTokenType);
257
+ // ~=
258
+ // ^=
259
+ // $=
260
+ // |=
261
+ if (peek() == '=') {
262
+ next();
263
+ switch (buffer.charAt(0)) {
264
+ case '~':
265
+ yield pushToken(buffer, EnumToken.IncludeMatchTokenType);
266
+ break;
267
+ case '^':
268
+ yield pushToken(buffer, EnumToken.StartMatchTokenType);
269
+ break;
270
+ case '$':
271
+ yield pushToken(buffer, EnumToken.EndMatchTokenType);
272
+ break;
273
+ case '|':
274
+ yield pushToken(buffer, EnumToken.DashMatchTokenType);
275
+ break;
276
+ }
251
277
  buffer = '';
252
278
  break;
253
279
  }
254
280
  yield pushToken(buffer);
255
- while (isWhiteSpace(value.charCodeAt(0))) {
256
- value = next();
257
- }
258
- buffer = value;
281
+ buffer = '';
259
282
  break;
260
283
  case '>':
261
284
  if (buffer !== '') {
@@ -289,7 +312,13 @@ function* tokenize(stream) {
289
312
  yield pushToken(buffer);
290
313
  buffer = '';
291
314
  }
292
- if (value == ':' && ':' == peek()) {
315
+ const val = peek();
316
+ if (val == '=') {
317
+ next();
318
+ yield pushToken(value + val, EnumToken.ContainMatchTokenType);
319
+ break;
320
+ }
321
+ if (value == ':' && ':' == val) {
293
322
  buffer += value + next();
294
323
  break;
295
324
  }
@@ -0,0 +1,67 @@
1
+ import { EnumToken } from '../../ast/types.js';
2
+ import '../../ast/minify.js';
3
+ import { walkValues } from '../../ast/walk.js';
4
+ import '../parse.js';
5
+ import { isWhiteSpace } from './syntax.js';
6
+ import '../../renderer/utils/color.js';
7
+ import '../../renderer/sourcemap/lib/encode.js';
8
+
9
+ function parseDeclaration(node, errors, src, position) {
10
+ while (node.val[0]?.typ == EnumToken.WhitespaceTokenType) {
11
+ node.val.shift();
12
+ }
13
+ if (node.val.filter((t) => ![EnumToken.WhitespaceTokenType, EnumToken.CommentTokenType].includes(t.typ)).length == 0) {
14
+ errors.push({
15
+ action: 'drop',
16
+ message: 'doParse: invalid declaration',
17
+ location: { src, ...position }
18
+ });
19
+ return null;
20
+ }
21
+ for (const { value: val, parent } of walkValues(node.val, node)) {
22
+ if (val.typ == EnumToken.AttrTokenType && val.chi.every((t) => [EnumToken.IdenTokenType, EnumToken.WhitespaceTokenType, EnumToken.CommentTokenType].includes(t.typ))) {
23
+ // @ts-ignore
24
+ val.typ = EnumToken.IdenListTokenType;
25
+ }
26
+ else if (val.typ == EnumToken.StringTokenType && (node.nam == 'grid' || node.nam == 'grid-template-areas' || node.nam == 'grid-template-rows' || node.nam == 'grid-template-columns')) {
27
+ val.val = val.val.at(0) + parseGridTemplate(val.val.slice(1, -1)) + val.val.at(-1);
28
+ // @ts-ignore
29
+ const array = parent?.chi ?? node.val;
30
+ const index = array.indexOf(val);
31
+ if (index > 0 && array[index - 1].typ == EnumToken.WhitespaceTokenType) {
32
+ array.splice(index - 1, 1);
33
+ }
34
+ }
35
+ }
36
+ return node;
37
+ }
38
+ function parseGridTemplate(template) {
39
+ let result = '';
40
+ let buffer = '';
41
+ for (let i = 0; i < template.length; i++) {
42
+ const char = template[i];
43
+ if (isWhiteSpace(char.codePointAt(0))) {
44
+ while (i + 1 < template.length && isWhiteSpace(template[i + 1].codePointAt(0))) {
45
+ i++;
46
+ }
47
+ result += buffer + ' ';
48
+ buffer = '';
49
+ }
50
+ else if (char == '.') {
51
+ while (i + 1 < template.length && template[i + 1] == '.') {
52
+ i++;
53
+ }
54
+ if (isWhiteSpace((result.at(-1)?.codePointAt(0)))) {
55
+ result = result.slice(0, -1);
56
+ }
57
+ result += buffer + char;
58
+ buffer = '';
59
+ }
60
+ else {
61
+ buffer += char;
62
+ }
63
+ }
64
+ return buffer.length > 0 ? result + buffer : result;
65
+ }
66
+
67
+ export { parseDeclaration };
@@ -37,10 +37,37 @@ function isColor(token) {
37
37
  // named color
38
38
  return token.val.toLowerCase() in COLORS_NAMES;
39
39
  }
40
+ let isLegacySyntax = false;
40
41
  if (token.typ == EnumToken.FunctionTokenType && token.chi.length > 0 && colorsFunc.includes(token.val)) {
42
+ const keywords = ['from', 'none'];
43
+ if (['rgb', 'hsl', 'hwb'].includes(token.val)) {
44
+ keywords.push('a', ...token.val.split(''));
45
+ }
46
+ // console.debug(JSON.stringify({token}, null, 1));
41
47
  // @ts-ignore
42
48
  for (const v of token.chi) {
43
- if (![EnumToken.NumberTokenType, EnumToken.AngleTokenType, EnumToken.PercentageTokenType, EnumToken.CommaTokenType, EnumToken.WhitespaceTokenType, EnumToken.LiteralTokenType].includes(v.typ)) {
49
+ // console.debug(JSON.stringify({v}, null, 1));
50
+ if (v.typ == EnumToken.CommaTokenType) {
51
+ isLegacySyntax = true;
52
+ }
53
+ if (v.typ == EnumToken.IdenTokenType) {
54
+ if (!(keywords.includes(v.val) || v.val.toLowerCase() in COLORS_NAMES)) {
55
+ return false;
56
+ }
57
+ if (keywords.includes(v.val)) {
58
+ if (isLegacySyntax) {
59
+ return false;
60
+ }
61
+ if (v.val == 'from' && ['rgba', 'hsla'].includes(token.val)) {
62
+ return false;
63
+ }
64
+ }
65
+ continue;
66
+ }
67
+ if (v.typ == EnumToken.FunctionTokenType && (v.val == 'calc' || colorsFunc.includes(v.val))) {
68
+ continue;
69
+ }
70
+ if (![EnumToken.ColorTokenType, EnumToken.IdenTokenType, EnumToken.NumberTokenType, EnumToken.AngleTokenType, EnumToken.PercentageTokenType, EnumToken.CommaTokenType, EnumToken.WhitespaceTokenType, EnumToken.LiteralTokenType].includes(v.typ)) {
44
71
  return false;
45
72
  }
46
73
  }
@@ -200,6 +227,9 @@ function isDimension(name) {
200
227
  function isPercentage(name) {
201
228
  return name.endsWith('%') && isNumber(name.slice(0, -1));
202
229
  }
230
+ function isFlex(name) {
231
+ return name.endsWith('fr') && isNumber(name.slice(0, -2));
232
+ }
203
233
  function parseDimension(name) {
204
234
  let index = name.length;
205
235
  while (index--) {
@@ -267,4 +297,4 @@ function isWhiteSpace(codepoint) {
267
297
  codepoint == 0xa || codepoint == 0xc || codepoint == 0xd;
268
298
  }
269
299
 
270
- export { isAngle, isAtKeyword, isColor, isDigit, isDimension, isFrequency, isFunction, isHash, isHexColor, isIdent, isIdentCodepoint, isIdentStart, isLength, isNewLine, isNonPrintable, isNumber, isPercentage, isPseudo, isResolution, isTime, isWhiteSpace, parseDimension };
300
+ export { isAngle, isAtKeyword, isColor, isDigit, isDimension, isFlex, isFrequency, isFunction, isHash, isHexColor, isIdent, isIdentCodepoint, isIdentStart, isLength, isNewLine, isNonPrintable, isNumber, isPercentage, isPseudo, isResolution, isTime, isWhiteSpace, parseDimension };
@@ -4,6 +4,7 @@ import '../parse.js';
4
4
  import '../../renderer/utils/color.js';
5
5
  import '../../renderer/sourcemap/lib/encode.js';
6
6
 
7
+ // https://www.w3.org/TR/css-values-4/#math-function
7
8
  const funcList = ['clamp', 'calc'];
8
9
  function matchType(val, properties) {
9
10
  if (val.typ == EnumToken.IdenTokenType && properties.keywords.includes(val.val) ||
@@ -19,8 +20,12 @@ function matchType(val, properties) {
19
20
  return typ == EnumToken.LengthTokenType || typ == EnumToken.AngleTokenType;
20
21
  });
21
22
  }
22
- if (val.typ == EnumToken.FunctionTokenType && funcList.includes(val.val)) {
23
- return val.chi.every((t => [EnumToken.LiteralTokenType, EnumToken.CommaTokenType, EnumToken.WhitespaceTokenType, EnumToken.StartParensTokenType, EnumToken.EndParensTokenType].includes(t.typ) || matchType(t, properties)));
23
+ if (val.typ == EnumToken.FunctionTokenType) {
24
+ if (funcList.includes(val.val)) {
25
+ return val.chi.every((t => [EnumToken.LiteralTokenType, EnumToken.CommaTokenType, EnumToken.WhitespaceTokenType, EnumToken.StartParensTokenType, EnumToken.EndParensTokenType].includes(t.typ) || matchType(t, properties)));
26
+ }
27
+ // match type defined like function 'symbols()', 'url()', 'attr()' etc.
28
+ // return properties.types.includes((<FunctionToken>val).val + '()')
24
29
  }
25
30
  return false;
26
31
  }
@@ -1,14 +1,16 @@
1
- import { getAngle, COLORS_NAMES, rgb2Hex, hsl2Hex, hwb2hex, cmyk2hex, NAMES_COLORS } from './utils/color.js';
1
+ import { getAngle, clamp, COLORS_NAMES, NAMES_COLORS } from './utils/color.js';
2
+ import { rgb2Hex, hsl2Hex, hwb2hex, cmyk2hex } from './utils/hex.js';
2
3
  import { EnumToken } from '../ast/types.js';
3
4
  import '../ast/minify.js';
4
5
  import { expand } from '../ast/expand.js';
5
6
  import { SourceMap } from './sourcemap/sourcemap.js';
6
7
  import '../parser/parse.js';
7
- import { isNewLine } from '../parser/utils/syntax.js';
8
+ import { isColor, isNewLine } from '../parser/utils/syntax.js';
9
+ import { parseRelativeColor } from './utils/calccolor.js';
8
10
 
9
11
  const colorsFunc = ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk'];
10
12
  function reduceNumber(val) {
11
- val = (+val).toString();
13
+ val = String(+val);
12
14
  if (val === '0') {
13
15
  return '0';
14
16
  }
@@ -47,7 +49,7 @@ function doRender(data, options = {}) {
47
49
  newLine: '\n',
48
50
  compress: false,
49
51
  removeComments: false,
50
- }), sourcemap: false, colorConvert: true, expandNestingRules: false, preserveLicense: false, ...options
52
+ }), sourcemap: false, convertColor: true, expandNestingRules: false, preserveLicense: false, ...options
51
53
  };
52
54
  const startTime = performance.now();
53
55
  const errors = [];
@@ -76,21 +78,19 @@ function doRender(data, options = {}) {
76
78
  }
77
79
  if (sourcemap != null) {
78
80
  result.map = sourcemap.toJSON();
79
- // @ts-ignore
80
- // result.map.sources = result.map.sources?.map(s => <string>options?.resolve(s, <string>options?.cwd)?.relative)
81
81
  }
82
82
  return result;
83
83
  }
84
84
  function updateSourceMap(node, options, cache, sourcemap, position, str) {
85
- if ([4 /* NodeType.RuleNodeType */, 3 /* NodeType.AtRuleNodeType */].includes(node.typ)) {
85
+ if ([EnumToken.RuleNodeType, EnumToken.AtRuleNodeType].includes(node.typ)) {
86
86
  let src = node.loc?.src ?? '';
87
87
  let output = options.output ?? '';
88
- if (src !== '') {
89
- if (!(src in cache)) {
90
- // @ts-ignore
91
- cache[src] = options.resolve(src, options.cwd ?? '').relative;
92
- }
88
+ // if (src !== '') {
89
+ if (!(src in cache)) {
90
+ // @ts-ignore
91
+ cache[src] = options.resolve(src, options.cwd ?? '').relative;
93
92
  }
93
+ // }
94
94
  if (!(output in cache)) {
95
95
  // @ts-ignore
96
96
  cache[output] = options.resolve(output, options.cwd).relative;
@@ -115,16 +115,16 @@ function renderAstNode(data, options, sourcemap, position, errors, reducer, cach
115
115
  const indent = indents[level];
116
116
  const indentSub = indents[level + 1];
117
117
  switch (data.typ) {
118
- case 5 /* NodeType.DeclarationNodeType */:
118
+ case EnumToken.DeclarationNodeType:
119
119
  return `${data.nam}:${options.indent}${data.val.reduce(reducer, '')}`;
120
- case 0 /* NodeType.CommentNodeType */:
121
- case 1 /* NodeType.CDOCOMMNodeType */:
120
+ case EnumToken.CommentNodeType:
121
+ case EnumToken.CDOCOMMNodeType:
122
122
  if (data.val.startsWith('# sourceMappingURL=')) {
123
123
  // ignore sourcemap
124
124
  return '';
125
125
  }
126
126
  return !options.removeComments || (options.preserveLicense && data.val.startsWith('/*!')) ? data.val : '';
127
- case 2 /* NodeType.StyleSheetNodeType */:
127
+ case EnumToken.StyleSheetNodeType:
128
128
  return data.chi.reduce((css, node) => {
129
129
  const str = renderAstNode(node, options, sourcemap, { ...position }, errors, reducer, cache, level, indents);
130
130
  if (str === '') {
@@ -142,18 +142,18 @@ function renderAstNode(data, options, sourcemap, position, errors, reducer, cach
142
142
  }
143
143
  return `${css}${options.newLine}${str}`;
144
144
  }, '');
145
- case 3 /* NodeType.AtRuleNodeType */:
146
- case 4 /* NodeType.RuleNodeType */:
147
- if (data.typ == 3 /* NodeType.AtRuleNodeType */ && !('chi' in data)) {
145
+ case EnumToken.AtRuleNodeType:
146
+ case EnumToken.RuleNodeType:
147
+ if (data.typ == EnumToken.AtRuleNodeType && !('chi' in data)) {
148
148
  return `${indent}@${data.nam}${data.val === '' ? '' : options.indent || ' '}${data.val};`;
149
149
  }
150
150
  // @ts-ignore
151
151
  let children = data.chi.reduce((css, node) => {
152
152
  let str;
153
- if (node.typ == 0 /* NodeType.CommentNodeType */) {
153
+ if (node.typ == EnumToken.CommentNodeType) {
154
154
  str = options.removeComments && (!options.preserveLicense || !node.val.startsWith('/*!')) ? '' : node.val;
155
155
  }
156
- else if (node.typ == 5 /* NodeType.DeclarationNodeType */) {
156
+ else if (node.typ == EnumToken.DeclarationNodeType) {
157
157
  if (node.val.length == 0) {
158
158
  // @ts-ignore
159
159
  errors.push({
@@ -165,7 +165,7 @@ function renderAstNode(data, options, sourcemap, position, errors, reducer, cach
165
165
  }
166
166
  str = `${node.nam}:${options.indent}${node.val.reduce(reducer, '').trimEnd()};`;
167
167
  }
168
- else if (node.typ == 3 /* NodeType.AtRuleNodeType */ && !('chi' in node)) {
168
+ else if (node.typ == EnumToken.AtRuleNodeType && !('chi' in node)) {
169
169
  str = `${data.val === '' ? '' : options.indent || ' '}${data.val};`;
170
170
  }
171
171
  else {
@@ -182,7 +182,7 @@ function renderAstNode(data, options, sourcemap, position, errors, reducer, cach
182
182
  if (children.endsWith(';')) {
183
183
  children = children.slice(0, -1);
184
184
  }
185
- if (data.typ == 3 /* NodeType.AtRuleNodeType */) {
185
+ if (data.typ == EnumToken.AtRuleNodeType) {
186
186
  return `@${data.nam}${data.val === '' ? '' : options.indent || ' '}${data.val}${options.indent}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}`;
187
187
  }
188
188
  return data.sel + `${options.indent}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}`;
@@ -201,11 +201,52 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer,
201
201
  return acc + renderToken(curr, options, cache, reducer, errors);
202
202
  };
203
203
  }
204
+ if (token.typ == EnumToken.FunctionTokenType && colorsFunc.includes(token.val)) {
205
+ if (isColor(token)) {
206
+ // @ts-ignore
207
+ token.typ = EnumToken.ColorTokenType;
208
+ if (token.chi[0].typ == EnumToken.IdenTokenType && token.chi[0].val == 'from') {
209
+ // @ts-ignore
210
+ token.cal = 'rel';
211
+ }
212
+ else {
213
+ token.chi = token.chi.filter((t) => ![EnumToken.WhitespaceTokenType, EnumToken.CommaTokenType, EnumToken.CommentTokenType].includes(t.typ));
214
+ }
215
+ }
216
+ }
204
217
  switch (token.typ) {
205
218
  case EnumToken.ListToken:
206
219
  return token.chi.reduce((acc, curr) => acc + renderToken(curr, options, cache), '');
207
220
  case EnumToken.BinaryExpressionTokenType:
221
+ if ([EnumToken.Mul, EnumToken.Div].includes(token.op)) {
222
+ let result = '';
223
+ if (token.l.typ == EnumToken.BinaryExpressionTokenType &&
224
+ [EnumToken.Add, EnumToken.Sub].includes(token.l.op)) {
225
+ result = '(' + renderToken(token.l, options, cache) + ')';
226
+ }
227
+ else {
228
+ result = renderToken(token.l, options, cache);
229
+ }
230
+ result += token.op == EnumToken.Mul ? '*' : '/';
231
+ if (token.r.typ == EnumToken.BinaryExpressionTokenType &&
232
+ [EnumToken.Add, EnumToken.Sub].includes(token.r.op)) {
233
+ result += '(' + renderToken(token.r, options, cache) + ')';
234
+ }
235
+ else {
236
+ result += renderToken(token.r, options, cache);
237
+ }
238
+ return result;
239
+ }
208
240
  return renderToken(token.l, options, cache) + (token.op == EnumToken.Add ? ' + ' : (token.op == EnumToken.Sub ? ' - ' : (token.op == EnumToken.Mul ? '*' : '/'))) + renderToken(token.r, options, cache);
241
+ case EnumToken.FractionTokenType:
242
+ const fraction = renderToken(token.l) + '/' + renderToken(token.r);
243
+ if (+token.r.val != 0) {
244
+ const value = reduceNumber(+token.l.val / +token.r.val);
245
+ if (value.length <= fraction.length) {
246
+ return value;
247
+ }
248
+ }
249
+ return fraction;
209
250
  case EnumToken.Add:
210
251
  return ' + ';
211
252
  case EnumToken.Sub:
@@ -215,10 +256,46 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer,
215
256
  case EnumToken.Div:
216
257
  return '/';
217
258
  case EnumToken.ColorTokenType:
218
- if (options.colorConvert) {
219
- if (token.kin == 'lit' && token.val.toLowerCase() == 'currentcolor') {
259
+ if (options.convertColor) {
260
+ if (token.cal == 'rel' && ['rgb', 'hsl', 'hwb'].includes(token.val)) {
261
+ const chi = token.chi.filter(x => ![
262
+ EnumToken.LiteralTokenType, EnumToken.CommaTokenType, EnumToken.WhitespaceTokenType, EnumToken.CommentTokenType
263
+ ].includes(x.typ));
264
+ const components = parseRelativeColor(token.val.split(''), chi[1], chi[2], chi[3], chi[4], chi[5]);
265
+ if (components != null) {
266
+ token.chi = Object.values(components);
267
+ delete token.cal;
268
+ }
269
+ }
270
+ if (token.cal) {
271
+ let slice = false;
272
+ if (token.cal == 'rel') {
273
+ const last = token.chi.at(-1);
274
+ if ((last.typ == EnumToken.NumberTokenType && last.val == '1') || (last.typ == EnumToken.IdenTokenType && last.val == 'none')) {
275
+ const prev = token.chi.at(-2);
276
+ if (prev.typ == EnumToken.LiteralTokenType && prev.val == '/') {
277
+ slice = true;
278
+ }
279
+ }
280
+ }
281
+ return clamp(token).val + '(' + (slice ? token.chi.slice(0, -2) : token.chi).reduce((acc, curr) => {
282
+ const val = renderToken(curr, options, cache);
283
+ if ([EnumToken.LiteralTokenType, EnumToken.CommaTokenType].includes(curr.typ)) {
284
+ return acc + val;
285
+ }
286
+ if (acc.length > 0) {
287
+ return acc + (['/', ','].includes(acc.at(-1)) ? '' : ' ') + val;
288
+ }
289
+ return val;
290
+ }, '') + ')';
291
+ }
292
+ if (token.kin == 'lit' && token.val.localeCompare('currentcolor', undefined, { sensitivity: 'base' }) == 0) {
220
293
  return 'currentcolor';
221
294
  }
295
+ clamp(token);
296
+ if (Array.isArray(token.chi) && token.chi.some((t) => t.typ == EnumToken.FunctionTokenType || (t.typ == EnumToken.ColorTokenType && Array.isArray(t.chi)))) {
297
+ return (token.val.endsWith('a') ? token.val.slice(0, -1) : token.val) + '(' + token.chi.reduce((acc, curr) => acc + (acc.length > 0 && !(acc.endsWith('/') || curr.typ == EnumToken.LiteralTokenType) ? ' ' : '') + renderToken(curr, options, cache), '') + ')';
298
+ }
222
299
  let value = token.kin == 'hex' ? token.val.toLowerCase() : (token.kin == 'lit' ? COLORS_NAMES[token.val.toLowerCase()] : '');
223
300
  if (token.val == 'rgb' || token.val == 'rgba') {
224
301
  value = rgb2Hex(token);
@@ -255,24 +332,52 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer,
255
332
  if (token.kin == 'hex' || token.kin == 'lit') {
256
333
  return token.val;
257
334
  }
335
+ if (Array.isArray(token.chi)) {
336
+ return (token.val.endsWith('a') ? token.val.slice(0, -1) : token.val) + '(' + token.chi.reduce((acc, curr) => acc + (acc.length > 0 && !(acc.endsWith('/') || curr.typ == EnumToken.LiteralTokenType) ? ' ' : '') + renderToken(curr, options, cache), '') + ')';
337
+ }
258
338
  case EnumToken.ParensTokenType:
259
339
  case EnumToken.FunctionTokenType:
260
340
  case EnumToken.UrlFunctionTokenType:
341
+ case EnumToken.ImageFunctionTokenType:
342
+ case EnumToken.TimingFunctionTokenType:
261
343
  case EnumToken.PseudoClassFuncTokenType:
344
+ case EnumToken.TimelineFunctionTokenType:
345
+ case EnumToken.GridTemplateFuncTokenType:
262
346
  if (token.typ == EnumToken.FunctionTokenType &&
263
347
  token.val == 'calc' &&
264
- token.chi.length == 1) {
348
+ token.chi.length == 1 &&
349
+ token.chi[0].typ != EnumToken.BinaryExpressionTokenType &&
350
+ token.chi[0].typ != EnumToken.FractionTokenType &&
351
+ token.chi[0].val?.typ != EnumToken.FractionTokenType) {
265
352
  // calc(200px) => 200px
266
353
  return token.chi.reduce((acc, curr) => acc + renderToken(curr, options, cache, reducer), '');
267
354
  }
268
355
  // @ts-ignore
269
356
  return ( /* options.minify && 'Pseudo-class-func' == token.typ && token.val.slice(0, 2) == '::' ? token.val.slice(1) :*/token.val ?? '') + '(' + token.chi.reduce(reducer, '') + ')';
357
+ case EnumToken.MatchExpressionTokenType:
358
+ return renderToken(token.l, options, cache, reducer, errors) +
359
+ renderToken({ typ: token.op }, options, cache, reducer, errors) +
360
+ renderToken(token.r, options, cache, reducer, errors) +
361
+ (token.attr ? ' ' + token.attr : '');
362
+ case EnumToken.NameSpaceAttributeTokenType:
363
+ return (token.l == null ? '' : renderToken(token.l, options, cache, reducer, errors) + '|') +
364
+ renderToken(token.r, options, cache, reducer, errors);
365
+ case EnumToken.BlockStartTokenType:
366
+ return '{';
367
+ case EnumToken.BlockEndTokenType:
368
+ return '}';
270
369
  case EnumToken.StartParensTokenType:
271
370
  return '(';
272
- case EnumToken.IncludesTokenType:
371
+ case EnumToken.IncludeMatchTokenType:
273
372
  return '~=';
274
373
  case EnumToken.DashMatchTokenType:
275
374
  return '|=';
375
+ case EnumToken.StartMatchTokenType:
376
+ return '^=';
377
+ case EnumToken.EndMatchTokenType:
378
+ return '$=';
379
+ case EnumToken.ContainMatchTokenType:
380
+ return '*=';
276
381
  case EnumToken.LtTokenType:
277
382
  return '<';
278
383
  case EnumToken.LteTokenType:
@@ -281,6 +386,8 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer,
281
386
  return '>';
282
387
  case EnumToken.GteTokenType:
283
388
  return '>=';
389
+ case EnumToken.ColumnCombinatorTokenType:
390
+ return '||';
284
391
  case EnumToken.EndParensTokenType:
285
392
  return ')';
286
393
  case EnumToken.AttrStartTokenType:
@@ -298,6 +405,7 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer,
298
405
  case EnumToken.ImportantTokenType:
299
406
  return '!important';
300
407
  case EnumToken.AttrTokenType:
408
+ case EnumToken.IdenListTokenType:
301
409
  return '[' + token.chi.reduce(reducer, '') + ']';
302
410
  case EnumToken.TimeTokenType:
303
411
  case EnumToken.AngleTokenType:
@@ -305,19 +413,9 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer,
305
413
  case EnumToken.DimensionTokenType:
306
414
  case EnumToken.FrequencyTokenType:
307
415
  case EnumToken.ResolutionTokenType:
308
- if (token.val.typ == EnumToken.BinaryExpressionTokenType) {
309
- const result = renderToken(token.val, options, cache);
310
- if (!('unit' in token)) {
311
- return result;
312
- }
313
- if (!result.includes(' ')) {
314
- return result + token.unit;
315
- }
316
- return `(${result})*1${token.unit}`;
317
- }
318
- let val = reduceNumber(token.val);
416
+ let val = token.val.typ == EnumToken.FractionTokenType ? renderToken(token.val, options, cache) : reduceNumber(token.val);
319
417
  let unit = token.unit;
320
- if (token.typ == EnumToken.AngleTokenType) {
418
+ if (token.typ == EnumToken.AngleTokenType && !val.includes('/')) {
321
419
  const angle = getAngle(token);
322
420
  let v;
323
421
  let value = val + unit;
@@ -374,16 +472,34 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer,
374
472
  }
375
473
  return '0';
376
474
  }
377
- return val + unit;
475
+ if (token.typ == EnumToken.TimeTokenType) {
476
+ if (unit == 'ms') {
477
+ // @ts-ignore
478
+ const v = reduceNumber(val / 1000);
479
+ if (v.length + 1 <= val.length) {
480
+ return v + 's';
481
+ }
482
+ return val + 'ms';
483
+ }
484
+ return val + 's';
485
+ }
486
+ return val.includes('/') ? val.replace('/', unit + '/') : val + unit;
487
+ case EnumToken.FlexTokenType:
378
488
  case EnumToken.PercentageTokenType:
379
- const perc = reduceNumber(token.val);
380
- return options.minify && perc == '0' ? '0' : perc + '%';
489
+ const uni = token.typ == EnumToken.PercentageTokenType ? '%' : 'fr';
490
+ const perc = token.val.typ == EnumToken.FractionTokenType ? renderToken(token.val, options, cache) : reduceNumber(token.val);
491
+ return options.minify && perc == '0' ? '0' : (perc.includes('/') ? perc.replace('/', uni + '/') : perc + uni);
381
492
  case EnumToken.NumberTokenType:
382
- return reduceNumber(token.val);
493
+ return token.val.typ == EnumToken.FractionTokenType ? renderToken(token.val, options, cache) : reduceNumber(token.val);
383
494
  case EnumToken.CommentTokenType:
384
495
  if (options.removeComments && (!options.preserveLicense || !token.val.startsWith('/*!'))) {
385
496
  return '';
386
497
  }
498
+ case EnumToken.PseudoClassTokenType:
499
+ // https://www.w3.org/TR/selectors-4/#single-colon-pseudos
500
+ if (token.typ == EnumToken.PseudoClassTokenType && ['::before', '::after', '::first-line', '::first-letter'].includes(token.val)) {
501
+ return token.val.slice(1);
502
+ }
387
503
  case EnumToken.UrlTokenTokenType:
388
504
  if (token.typ == EnumToken.UrlTokenTokenType) {
389
505
  if (options.output != null) {
@@ -407,13 +523,13 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer,
407
523
  token.val = cache[token.original];
408
524
  }
409
525
  }
410
- case EnumToken.AtRuleTokenType:
411
526
  case EnumToken.HashTokenType:
412
- case EnumToken.PseudoClassTokenType:
413
- case EnumToken.LiteralTokenType:
414
- case EnumToken.StringTokenType:
415
527
  case EnumToken.IdenTokenType:
416
528
  case EnumToken.DelimTokenType:
529
+ case EnumToken.AtRuleTokenType:
530
+ case EnumToken.StringTokenType:
531
+ case EnumToken.LiteralTokenType:
532
+ case EnumToken.DashedIdenTokenType:
417
533
  return /* options.minify && 'Pseudo-class' == token.typ && '::' == token.val.slice(0, 2) ? token.val.slice(1) : */ token.val;
418
534
  }
419
535
  errors?.push({ action: 'ignore', message: `render: unexpected token ${JSON.stringify(token, null, 1)}` });