cssstyle 5.3.4 → 5.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/lib/CSSStyleDeclaration.js +2 -2
  2. package/lib/generated/implementedProperties.js +245 -1096
  3. package/lib/generated/properties.js +1959 -2272
  4. package/lib/generated/propertyDefinitions.js +13033 -0
  5. package/lib/normalize.js +1259 -1095
  6. package/lib/parsers.js +588 -228
  7. package/lib/properties/background.js +89 -79
  8. package/lib/properties/backgroundAttachment.js +11 -14
  9. package/lib/properties/backgroundClip.js +11 -14
  10. package/lib/properties/backgroundColor.js +8 -13
  11. package/lib/properties/backgroundImage.js +11 -26
  12. package/lib/properties/backgroundOrigin.js +11 -14
  13. package/lib/properties/backgroundPosition.js +29 -18
  14. package/lib/properties/backgroundRepeat.js +11 -7
  15. package/lib/properties/backgroundSize.js +22 -16
  16. package/lib/properties/border.js +30 -87
  17. package/lib/properties/borderBottom.js +30 -86
  18. package/lib/properties/borderBottomColor.js +8 -14
  19. package/lib/properties/borderBottomStyle.js +8 -13
  20. package/lib/properties/borderBottomWidth.js +11 -23
  21. package/lib/properties/borderCollapse.js +4 -9
  22. package/lib/properties/borderColor.js +17 -23
  23. package/lib/properties/borderLeft.js +30 -86
  24. package/lib/properties/borderLeftColor.js +8 -14
  25. package/lib/properties/borderLeftStyle.js +8 -13
  26. package/lib/properties/borderLeftWidth.js +11 -23
  27. package/lib/properties/borderRight.js +30 -86
  28. package/lib/properties/borderRightColor.js +8 -14
  29. package/lib/properties/borderRightStyle.js +8 -13
  30. package/lib/properties/borderRightWidth.js +11 -23
  31. package/lib/properties/borderSpacing.js +12 -9
  32. package/lib/properties/borderStyle.js +17 -23
  33. package/lib/properties/borderTop.js +30 -86
  34. package/lib/properties/borderTopColor.js +8 -14
  35. package/lib/properties/borderTopStyle.js +8 -13
  36. package/lib/properties/borderTopWidth.js +11 -23
  37. package/lib/properties/borderWidth.js +18 -36
  38. package/lib/properties/bottom.js +6 -17
  39. package/lib/properties/clear.js +4 -9
  40. package/lib/properties/clip.js +11 -6
  41. package/lib/properties/color.js +4 -10
  42. package/lib/properties/display.js +11 -8
  43. package/lib/properties/flex.js +55 -53
  44. package/lib/properties/flexBasis.js +10 -20
  45. package/lib/properties/flexGrow.js +10 -19
  46. package/lib/properties/flexShrink.js +10 -19
  47. package/lib/properties/float.js +4 -9
  48. package/lib/properties/floodColor.js +4 -10
  49. package/lib/properties/font.js +46 -33
  50. package/lib/properties/fontFamily.js +16 -12
  51. package/lib/properties/fontSize.js +11 -22
  52. package/lib/properties/fontStyle.js +14 -8
  53. package/lib/properties/fontVariant.js +11 -18
  54. package/lib/properties/fontWeight.js +14 -25
  55. package/lib/properties/height.js +7 -17
  56. package/lib/properties/left.js +6 -17
  57. package/lib/properties/lightingColor.js +4 -10
  58. package/lib/properties/lineHeight.js +10 -24
  59. package/lib/properties/margin.js +14 -32
  60. package/lib/properties/marginBottom.js +10 -20
  61. package/lib/properties/marginLeft.js +10 -20
  62. package/lib/properties/marginRight.js +10 -20
  63. package/lib/properties/marginTop.js +10 -20
  64. package/lib/properties/opacity.js +6 -18
  65. package/lib/properties/outlineColor.js +4 -10
  66. package/lib/properties/padding.js +15 -30
  67. package/lib/properties/paddingBottom.js +11 -21
  68. package/lib/properties/paddingLeft.js +11 -21
  69. package/lib/properties/paddingRight.js +11 -21
  70. package/lib/properties/paddingTop.js +11 -21
  71. package/lib/properties/right.js +6 -17
  72. package/lib/properties/stopColor.js +4 -10
  73. package/lib/properties/top.js +7 -17
  74. package/lib/properties/webkitBorderAfterColor.js +4 -10
  75. package/lib/properties/webkitBorderBeforeColor.js +4 -10
  76. package/lib/properties/webkitBorderEndColor.js +4 -10
  77. package/lib/properties/webkitBorderStartColor.js +4 -10
  78. package/lib/properties/webkitColumnRuleColor.js +4 -10
  79. package/lib/properties/webkitTapHighlightColor.js +4 -10
  80. package/lib/properties/webkitTextEmphasisColor.js +4 -10
  81. package/lib/properties/webkitTextFillColor.js +4 -10
  82. package/lib/properties/webkitTextStrokeColor.js +4 -10
  83. package/lib/properties/width.js +7 -17
  84. package/lib/utils/propertyDescriptors.js +49 -13
  85. package/lib/utils/strings.js +6 -0
  86. package/package.json +16 -40
package/lib/parsers.js CHANGED
@@ -6,16 +6,17 @@ const {
6
6
  } = require("@asamuzakjp/css-color");
7
7
  const { next: syntaxes } = require("@csstools/css-syntax-patches-for-csstree");
8
8
  const csstree = require("css-tree");
9
+ const { LRUCache } = require("lru-cache");
9
10
  const { asciiLowercase } = require("./utils/strings");
10
11
 
11
12
  // CSS global keywords
12
13
  // @see https://drafts.csswg.org/css-cascade-5/#defaulting-keywords
13
- const GLOBAL_KEY = Object.freeze(["initial", "inherit", "unset", "revert", "revert-layer"]);
14
+ const GLOBAL_KEYS = new Set(["initial", "inherit", "unset", "revert", "revert-layer"]);
14
15
 
15
16
  // System colors
16
17
  // @see https://drafts.csswg.org/css-color/#css-system-colors
17
18
  // @see https://drafts.csswg.org/css-color/#deprecated-system-colors
18
- const SYS_COLOR = Object.freeze([
19
+ const SYS_COLORS = new Set([
19
20
  "accentcolor",
20
21
  "accentcolortext",
21
22
  "activeborder",
@@ -60,6 +61,20 @@ const SYS_COLOR = Object.freeze([
60
61
  "windowtext"
61
62
  ]);
62
63
 
64
+ // AST node types
65
+ const AST_TYPES = Object.freeze({
66
+ CALC: "Calc",
67
+ DIMENSION: "Dimension",
68
+ FUNCTION: "Function",
69
+ GLOBAL_KEYWORD: "GlobalKeyword",
70
+ HASH: "Hash",
71
+ IDENTIFIER: "Identifier",
72
+ NUMBER: "Number",
73
+ PERCENTAGE: "Percentage",
74
+ STRING: "String",
75
+ URL: "Url"
76
+ });
77
+
63
78
  // Regular expressions
64
79
  const CALC_FUNC_NAMES =
65
80
  "(?:a?(?:cos|sin|tan)|abs|atan2|calc|clamp|exp|hypot|log|max|min|mod|pow|rem|round|sign|sqrt)";
@@ -72,8 +87,19 @@ const varContainedRegEx = /(?<=[*/\s(])var\(/;
72
87
  // Patched css-tree
73
88
  const cssTree = csstree.fork(syntaxes);
74
89
 
75
- // Prepare stringified value.
76
- exports.prepareValue = (value, globalObject = globalThis) => {
90
+ // Instance of the LRU Cache. Stores up to 4096 items.
91
+ const lruCache = new LRUCache({
92
+ max: 4096
93
+ });
94
+
95
+ /**
96
+ * Prepares a stringified value.
97
+ *
98
+ * @param {string|number|null|undefined} value - The value to prepare.
99
+ * @param {object} [globalObject=globalThis] - The global object.
100
+ * @returns {string} The prepared value.
101
+ */
102
+ const prepareValue = (value, globalObject = globalThis) => {
77
103
  // `null` is converted to an empty string.
78
104
  // @see https://webidl.spec.whatwg.org/#LegacyNullToEmptyString
79
105
  if (value === null) {
@@ -99,29 +125,47 @@ exports.prepareValue = (value, globalObject = globalThis) => {
99
125
  }
100
126
  };
101
127
 
102
- // Value is a global keyword.
103
- exports.isGlobalKeyword = (val) => {
104
- return GLOBAL_KEY.includes(asciiLowercase(val));
128
+ /**
129
+ * Checks if the value is a global keyword.
130
+ *
131
+ * @param {string} val - The value to check.
132
+ * @returns {boolean} True if the value is a global keyword, false otherwise.
133
+ */
134
+ const isGlobalKeyword = (val) => {
135
+ return GLOBAL_KEYS.has(asciiLowercase(val));
105
136
  };
106
137
 
107
- // Value starts with and/or contains CSS var() function.
108
- exports.hasVarFunc = (val) => {
138
+ /**
139
+ * Checks if the value starts with or contains a CSS var() function.
140
+ *
141
+ * @param {string} val - The value to check.
142
+ * @returns {boolean} True if the value contains a var() function, false otherwise.
143
+ */
144
+ const hasVarFunc = (val) => {
109
145
  return varRegEx.test(val) || varContainedRegEx.test(val);
110
146
  };
111
147
 
112
- // Value starts with and/or contains CSS calc() related functions.
113
- exports.hasCalcFunc = (val) => {
148
+ /**
149
+ * Checks if the value starts with or contains CSS calc() or math functions.
150
+ *
151
+ * @param {string} val - The value to check.
152
+ * @returns {boolean} True if the value contains calc() or math functions, false otherwise.
153
+ */
154
+ const hasCalcFunc = (val) => {
114
155
  return calcRegEx.test(val) || calcContainedRegEx.test(val);
115
156
  };
116
157
 
117
- // Splits value into an array.
118
- // @see https://github.com/asamuzaK/cssColor/blob/main/src/js/util.ts
119
- exports.splitValue = splitValue;
120
-
121
- // Parse CSS to AST.
122
- exports.parseCSS = (val, opt, toObject = false) => {
158
+ /**
159
+ * Parses a CSS string into an AST.
160
+ *
161
+ * @param {string} val - The CSS string to parse.
162
+ * @param {object} opt - The options for parsing.
163
+ * @param {boolean} [toObject=false] - Whether to return a plain object.
164
+ * @returns {object} The AST or a plain object.
165
+ */
166
+ const parseCSS = (val, opt, toObject = false) => {
123
167
  if (typeof val !== "string") {
124
- val = exports.prepareValue(val);
168
+ val = prepareValue(val);
125
169
  }
126
170
  const ast = cssTree.parse(val, opt);
127
171
  if (toObject) {
@@ -130,11 +174,17 @@ exports.parseCSS = (val, opt, toObject = false) => {
130
174
  return ast;
131
175
  };
132
176
 
133
- // Value is a valid property value.
134
- // Returns `false` for custom property and/or var().
135
- exports.isValidPropertyValue = (prop, val) => {
177
+ /**
178
+ * Checks if the value is a valid property value.
179
+ * Returns false for custom properties or values containing var().
180
+ *
181
+ * @param {string} prop - The property name.
182
+ * @param {string} val - The property value.
183
+ * @returns {boolean} True if the value is valid, false otherwise.
184
+ */
185
+ const isValidPropertyValue = (prop, val) => {
136
186
  if (typeof val !== "string") {
137
- val = exports.prepareValue(val);
187
+ val = prepareValue(val);
138
188
  }
139
189
  if (val === "") {
140
190
  return true;
@@ -142,33 +192,51 @@ exports.isValidPropertyValue = (prop, val) => {
142
192
  // cssTree.lexer does not support deprecated system colors
143
193
  // @see https://github.com/w3c/webref/issues/1519#issuecomment-3120290261
144
194
  // @see https://github.com/w3c/webref/issues/1647
145
- if (SYS_COLOR.includes(asciiLowercase(val))) {
195
+ if (SYS_COLORS.has(asciiLowercase(val))) {
146
196
  if (/^(?:-webkit-)?(?:[a-z][a-z\d]*-)*color$/i.test(prop)) {
147
197
  return true;
148
198
  }
149
199
  return false;
150
200
  }
151
- let ast;
201
+ const cacheKey = `isValidPropertyValue_${prop}_${val}`;
202
+ const cachedValue = lruCache.get(cacheKey);
203
+ if (typeof cachedValue === "boolean") {
204
+ return cachedValue;
205
+ }
206
+ let result;
152
207
  try {
153
- ast = exports.parseCSS(val, {
208
+ const ast = parseCSS(val, {
154
209
  context: "value"
155
210
  });
211
+ const { error, matched } = cssTree.lexer.matchProperty(prop, ast);
212
+ result = error === null && matched !== null;
156
213
  } catch {
157
- return false;
214
+ result = false;
158
215
  }
159
- const { error, matched } = cssTree.lexer.matchProperty(prop, ast);
160
- return error === null && matched !== null;
216
+ lruCache.set(cacheKey, result);
217
+ return result;
161
218
  };
162
219
 
163
- // Simplify / resolve math functions.
164
- exports.resolveCalc = (val, opt = { format: "specifiedValue" }) => {
220
+ /**
221
+ * Resolves CSS math functions.
222
+ *
223
+ * @param {string} val - The value to resolve.
224
+ * @param {object} [opt={ format: "specifiedValue" }] - The options for resolving.
225
+ * @returns {string|undefined} The resolved value.
226
+ */
227
+ const resolveCalc = (val, opt = { format: "specifiedValue" }) => {
165
228
  if (typeof val !== "string") {
166
- val = exports.prepareValue(val);
229
+ val = prepareValue(val);
167
230
  }
168
- if (val === "" || exports.hasVarFunc(val) || !exports.hasCalcFunc(val)) {
231
+ if (val === "" || hasVarFunc(val) || !hasCalcFunc(val)) {
169
232
  return val;
170
233
  }
171
- const obj = exports.parseCSS(val, { context: "value" }, true);
234
+ const cacheKey = `resolveCalc_${val}`;
235
+ const cachedValue = lruCache.get(cacheKey);
236
+ if (typeof cachedValue === "string") {
237
+ return cachedValue;
238
+ }
239
+ const obj = parseCSS(val, { context: "value" }, true);
172
240
  if (!obj?.children) {
173
241
  return;
174
242
  }
@@ -176,7 +244,7 @@ exports.resolveCalc = (val, opt = { format: "specifiedValue" }) => {
176
244
  const values = [];
177
245
  for (const item of items) {
178
246
  const { type: itemType, name: itemName, value: itemValue } = item;
179
- if (itemType === "Function") {
247
+ if (itemType === AST_TYPES.FUNCTION) {
180
248
  const value = cssTree
181
249
  .generate(item)
182
250
  .replace(/\)(?!\)|\s|,)/g, ") ")
@@ -187,23 +255,33 @@ exports.resolveCalc = (val, opt = { format: "specifiedValue" }) => {
187
255
  } else {
188
256
  values.push(value);
189
257
  }
190
- } else if (itemType === "String") {
258
+ } else if (itemType === AST_TYPES.STRING) {
191
259
  values.push(`"${itemValue}"`);
192
260
  } else {
193
261
  values.push(itemName ?? itemValue);
194
262
  }
195
263
  }
196
- return values.join(" ");
264
+ const resolvedValue = values.join(" ");
265
+ lruCache.set(cacheKey, resolvedValue);
266
+ return resolvedValue;
197
267
  };
198
268
 
199
- // Parse property value. Returns string or array of parsed object.
200
- exports.parsePropertyValue = (prop, val, opt = {}) => {
269
+ /**
270
+ * Parses a property value.
271
+ * Returns a string or an array of parsed objects.
272
+ *
273
+ * @param {string} prop - The property name.
274
+ * @param {string} val - The property value.
275
+ * @param {object} [opt={}] - The options for parsing.
276
+ * @returns {string|Array<object>|undefined} The parsed value.
277
+ */
278
+ const parsePropertyValue = (prop, val, opt = {}) => {
201
279
  const { caseSensitive, globalObject, inArray } = opt;
202
- val = exports.prepareValue(val, globalObject);
203
- if (val === "" || exports.hasVarFunc(val)) {
280
+ val = prepareValue(val, globalObject);
281
+ if (val === "" || hasVarFunc(val)) {
204
282
  return val;
205
- } else if (exports.hasCalcFunc(val)) {
206
- const calculatedValue = exports.resolveCalc(val, {
283
+ } else if (hasCalcFunc(val)) {
284
+ const calculatedValue = resolveCalc(val, {
207
285
  format: "specifiedValue"
208
286
  });
209
287
  if (typeof calculatedValue !== "string") {
@@ -211,137 +289,164 @@ exports.parsePropertyValue = (prop, val, opt = {}) => {
211
289
  }
212
290
  val = calculatedValue;
213
291
  }
292
+ const cacheKey = `parsePropertyValue_${prop}_${val}_${caseSensitive}`;
293
+ const cachedValue = lruCache.get(cacheKey);
294
+ if (cachedValue === false) {
295
+ return;
296
+ } else if (inArray) {
297
+ if (Array.isArray(cachedValue)) {
298
+ return cachedValue;
299
+ }
300
+ } else if (typeof cachedValue === "string") {
301
+ return cachedValue;
302
+ }
303
+ let parsedValue;
214
304
  const lowerCasedValue = asciiLowercase(val);
215
- if (GLOBAL_KEY.includes(lowerCasedValue)) {
305
+ if (GLOBAL_KEYS.has(lowerCasedValue)) {
216
306
  if (inArray) {
217
- return [
307
+ parsedValue = [
218
308
  {
219
- type: "GlobalKeyword",
309
+ type: AST_TYPES.GLOBAL_KEYWORD,
220
310
  name: lowerCasedValue
221
311
  }
222
312
  ];
313
+ } else {
314
+ parsedValue = lowerCasedValue;
223
315
  }
224
- return lowerCasedValue;
225
- } else if (SYS_COLOR.includes(lowerCasedValue)) {
316
+ } else if (SYS_COLORS.has(lowerCasedValue)) {
226
317
  if (/^(?:(?:-webkit-)?(?:[a-z][a-z\d]*-)*color|border)$/i.test(prop)) {
227
318
  if (inArray) {
228
- return [
319
+ parsedValue = [
229
320
  {
230
- type: "Identifier",
321
+ type: AST_TYPES.IDENTIFIER,
231
322
  name: lowerCasedValue
232
323
  }
233
324
  ];
325
+ } else {
326
+ parsedValue = lowerCasedValue;
234
327
  }
235
- return lowerCasedValue;
236
- }
237
- return;
238
- }
239
- try {
240
- const ast = exports.parseCSS(val, {
241
- context: "value"
242
- });
243
- const { error, matched } = cssTree.lexer.matchProperty(prop, ast);
244
- if (error || !matched) {
245
- return;
328
+ } else {
329
+ parsedValue = false;
246
330
  }
247
- if (inArray) {
248
- const obj = cssTree.toPlainObject(ast);
249
- const items = obj.children;
250
- const parsedValues = [];
251
- for (const item of items) {
252
- const { children, name, type, value, unit } = item;
253
- switch (type) {
254
- case "Dimension": {
255
- parsedValues.push({
256
- type,
257
- value,
258
- unit: asciiLowercase(unit)
259
- });
260
- break;
261
- }
262
- case "Function": {
263
- const css = cssTree
264
- .generate(item)
265
- .replace(/\)(?!\)|\s|,)/g, ") ")
266
- .trim();
267
- const raw = items.length === 1 ? val : css;
268
- const itemValue = raw
269
- .replace(new RegExp(`^${name}\\(`), "")
270
- .replace(/\)$/, "")
271
- .trim();
272
- if (name === "calc") {
273
- if (children.length === 1) {
274
- const [child] = children;
275
- if (child.type === "Number") {
276
- parsedValues.push({
277
- type: "Calc",
278
- name: "calc",
279
- isNumber: true,
280
- value: `${parseFloat(child.value)}`,
281
- raw
282
- });
331
+ } else {
332
+ try {
333
+ const ast = parseCSS(val, {
334
+ context: "value"
335
+ });
336
+ const { error, matched } = cssTree.lexer.matchProperty(prop, ast);
337
+ if (error || !matched) {
338
+ parsedValue = false;
339
+ } else if (inArray) {
340
+ const obj = cssTree.toPlainObject(ast);
341
+ const items = obj.children;
342
+ const values = [];
343
+ for (const item of items) {
344
+ const { children, name, type, value, unit } = item;
345
+ switch (type) {
346
+ case AST_TYPES.DIMENSION: {
347
+ values.push({
348
+ type,
349
+ value,
350
+ unit: asciiLowercase(unit)
351
+ });
352
+ break;
353
+ }
354
+ case AST_TYPES.FUNCTION: {
355
+ const css = cssTree
356
+ .generate(item)
357
+ .replace(/\)(?!\)|\s|,)/g, ") ")
358
+ .trim();
359
+ const raw = items.length === 1 ? val : css;
360
+ // Remove "${name}(" from the start and ")" from the end
361
+ const itemValue = raw.slice(name.length + 1, -1).trim();
362
+ if (name === "calc") {
363
+ if (children.length === 1) {
364
+ const [child] = children;
365
+ if (child.type === AST_TYPES.NUMBER) {
366
+ values.push({
367
+ type: AST_TYPES.CALC,
368
+ isNumber: true,
369
+ value: `${parseFloat(child.value)}`,
370
+ name,
371
+ raw
372
+ });
373
+ } else {
374
+ values.push({
375
+ type: AST_TYPES.CALC,
376
+ isNumber: false,
377
+ value: `${asciiLowercase(itemValue)}`,
378
+ name,
379
+ raw
380
+ });
381
+ }
283
382
  } else {
284
- parsedValues.push({
285
- type: "Calc",
286
- name: "calc",
383
+ values.push({
384
+ type: AST_TYPES.CALC,
287
385
  isNumber: false,
288
- value: `${asciiLowercase(itemValue)}`,
386
+ value: asciiLowercase(itemValue),
387
+ name,
289
388
  raw
290
389
  });
291
390
  }
292
391
  } else {
293
- parsedValues.push({
294
- type: "Calc",
295
- name: "calc",
296
- isNumber: false,
392
+ values.push({
393
+ type,
394
+ name,
297
395
  value: asciiLowercase(itemValue),
298
396
  raw
299
397
  });
300
398
  }
301
- } else {
302
- parsedValues.push({
303
- type,
304
- name,
305
- value: asciiLowercase(itemValue),
306
- raw
307
- });
399
+ break;
308
400
  }
309
- break;
310
- }
311
- case "Identifier": {
312
- if (caseSensitive) {
313
- parsedValues.push(item);
314
- } else {
315
- parsedValues.push({
316
- type,
317
- name: asciiLowercase(name)
318
- });
401
+ case AST_TYPES.IDENTIFIER: {
402
+ if (caseSensitive) {
403
+ values.push(item);
404
+ } else {
405
+ values.push({
406
+ type,
407
+ name: asciiLowercase(name)
408
+ });
409
+ }
410
+ break;
411
+ }
412
+ default: {
413
+ values.push(item);
319
414
  }
320
- break;
321
- }
322
- default: {
323
- parsedValues.push(item);
324
415
  }
325
416
  }
417
+ parsedValue = values;
418
+ } else {
419
+ parsedValue = val;
326
420
  }
327
- return parsedValues;
421
+ } catch {
422
+ parsedValue = false;
328
423
  }
329
- } catch {
424
+ }
425
+ lruCache.set(cacheKey, parsedValue);
426
+ if (parsedValue === false) {
330
427
  return;
331
428
  }
332
- return val;
429
+ return parsedValue;
333
430
  };
334
431
 
335
- // Parse <number>.
336
- exports.parseNumber = (val, opt = {}) => {
432
+ /**
433
+ * Parses a numeric value (number, dimension, percentage).
434
+ * Helper function for parseNumber, parseLength, etc.
435
+ *
436
+ * @param {Array<object>} val - The AST value.
437
+ * @param {object} [opt={}] - The options for parsing.
438
+ * @param {Function} validateType - Function to validate the node type.
439
+ * @returns {object|undefined} The parsed result containing num and unit, or undefined.
440
+ */
441
+ const parseNumericValue = (val, opt, validateType) => {
337
442
  const [item] = val;
338
- const { type, value } = item ?? {};
339
- if (type !== "Number") {
443
+ const { type, value, unit } = item ?? {};
444
+ if (!validateType(type, value, unit)) {
340
445
  return;
341
446
  }
342
- const { clamp } = opt;
343
- const max = opt.max ?? Number.INFINITY;
344
- const min = opt.min ?? Number.NEGATIVE_INFINITY;
447
+ const { clamp } = opt || {};
448
+ const max = opt?.max ?? Number.INFINITY;
449
+ const min = opt?.min ?? Number.NEGATIVE_INFINITY;
345
450
  let num = parseFloat(value);
346
451
  if (clamp) {
347
452
  if (num > max) {
@@ -352,140 +457,142 @@ exports.parseNumber = (val, opt = {}) => {
352
457
  } else if (num > max || num < min) {
353
458
  return;
354
459
  }
355
- return `${num}`;
460
+ return {
461
+ num,
462
+ unit: unit ? asciiLowercase(unit) : null,
463
+ type
464
+ };
356
465
  };
357
466
 
358
- // Parse <length>.
359
- exports.parseLength = (val, opt = {}) => {
360
- const [item] = val;
361
- const { type, value, unit } = item ?? {};
362
- if (type !== "Dimension" && !(type === "Number" && value === "0")) {
467
+ /**
468
+ * Parses a <number> value.
469
+ *
470
+ * @param {Array<object>} val - The AST value.
471
+ * @param {object} [opt={}] - The options for parsing.
472
+ * @returns {string|undefined} The parsed number.
473
+ */
474
+ const parseNumber = (val, opt = {}) => {
475
+ const res = parseNumericValue(val, opt, (type) => type === AST_TYPES.NUMBER);
476
+ if (!res) {
363
477
  return;
364
478
  }
365
- const { clamp } = opt;
366
- const max = opt.max ?? Number.INFINITY;
367
- const min = opt.min ?? Number.NEGATIVE_INFINITY;
368
- let num = parseFloat(value);
369
- if (clamp) {
370
- if (num > max) {
371
- num = max;
372
- } else if (num < min) {
373
- num = min;
374
- }
375
- } else if (num > max || num < min) {
479
+ return `${res.num}`;
480
+ };
481
+
482
+ /**
483
+ * Parses a <length> value.
484
+ *
485
+ * @param {Array<object>} val - The AST value.
486
+ * @param {object} [opt={}] - The options for parsing.
487
+ * @returns {string|undefined} The parsed length.
488
+ */
489
+ const parseLength = (val, opt = {}) => {
490
+ const res = parseNumericValue(
491
+ val,
492
+ opt,
493
+ (type, value) => type === AST_TYPES.DIMENSION || (type === AST_TYPES.NUMBER && value === "0")
494
+ );
495
+ if (!res) {
376
496
  return;
377
497
  }
498
+ const { num, unit } = res;
378
499
  if (num === 0 && !unit) {
379
500
  return `${num}px`;
380
501
  } else if (unit) {
381
- return `${num}${asciiLowercase(unit)}`;
502
+ return `${num}${unit}`;
382
503
  }
383
504
  };
384
505
 
385
- // Parse <percentage>.
386
- exports.parsePercentage = (val, opt = {}) => {
387
- const [item] = val;
388
- const { type, value } = item ?? {};
389
- if (type !== "Percentage" && !(type === "Number" && value === "0")) {
390
- return;
391
- }
392
- const { clamp } = opt;
393
- const max = opt.max ?? Number.INFINITY;
394
- const min = opt.min ?? Number.NEGATIVE_INFINITY;
395
- let num = parseFloat(value);
396
- if (clamp) {
397
- if (num > max) {
398
- num = max;
399
- } else if (num < min) {
400
- num = min;
401
- }
402
- } else if (num > max || num < min) {
506
+ /**
507
+ * Parses a <percentage> value.
508
+ *
509
+ * @param {Array<object>} val - The AST value.
510
+ * @param {object} [opt={}] - The options for parsing.
511
+ * @returns {string|undefined} The parsed percentage.
512
+ */
513
+ const parsePercentage = (val, opt = {}) => {
514
+ const res = parseNumericValue(
515
+ val,
516
+ opt,
517
+ (type, value) => type === AST_TYPES.PERCENTAGE || (type === AST_TYPES.NUMBER && value === "0")
518
+ );
519
+ if (!res) {
403
520
  return;
404
521
  }
405
- if (num === 0) {
406
- return `${num}%`;
407
- }
522
+ const { num } = res;
408
523
  return `${num}%`;
409
524
  };
410
525
 
411
- // Parse <length-percentage>.
412
- exports.parseLengthPercentage = (val, opt = {}) => {
413
- const [item] = val;
414
- const { type, value, unit } = item ?? {};
415
- if (type !== "Dimension" && type !== "Percentage" && !(type === "Number" && value === "0")) {
416
- return;
417
- }
418
- const { clamp } = opt;
419
- const max = opt.max ?? Number.INFINITY;
420
- const min = opt.min ?? Number.NEGATIVE_INFINITY;
421
- let num = parseFloat(value);
422
- if (clamp) {
423
- if (num > max) {
424
- num = max;
425
- } else if (num < min) {
426
- num = min;
427
- }
428
- } else if (num > max || num < min) {
526
+ /**
527
+ * Parses an <angle> value.
528
+ *
529
+ * @param {Array<object>} val - The AST value.
530
+ * @param {object} [opt={}] - The options for parsing.
531
+ * @returns {string|undefined} The parsed angle.
532
+ */
533
+ const parseAngle = (val, opt = {}) => {
534
+ const res = parseNumericValue(
535
+ val,
536
+ opt,
537
+ (type, value) => type === AST_TYPES.DIMENSION || (type === AST_TYPES.NUMBER && value === "0")
538
+ );
539
+ if (!res) {
429
540
  return;
430
541
  }
431
- if (unit) {
432
- if (/deg|g?rad|turn/i.test(unit)) {
433
- return;
434
- }
435
- return `${num}${asciiLowercase(unit)}`;
436
- } else if (type === "Percentage") {
437
- return `${num}%`;
438
- } else if (num === 0) {
439
- return `${num}px`;
440
- }
441
- };
442
-
443
- // Parse <angle>.
444
- exports.parseAngle = (val) => {
445
- const [item] = val;
446
- const { type, value, unit } = item ?? {};
447
- if (type !== "Dimension" && !(type === "Number" && value === "0")) {
448
- return;
449
- }
450
- const num = parseFloat(value);
542
+ const { num, unit } = res;
451
543
  if (unit) {
452
544
  if (!/^(?:deg|g?rad|turn)$/i.test(unit)) {
453
545
  return;
454
546
  }
455
- return `${num}${asciiLowercase(unit)}`;
547
+ return `${num}${unit}`;
456
548
  } else if (num === 0) {
457
549
  return `${num}deg`;
458
550
  }
459
551
  };
460
552
 
461
- // Parse <url>.
462
- exports.parseUrl = (val) => {
553
+ /**
554
+ * Parses a <url> value.
555
+ *
556
+ * @param {Array<object>} val - The AST value.
557
+ * @returns {string|undefined} The parsed url.
558
+ */
559
+ const parseUrl = (val) => {
463
560
  const [item] = val;
464
561
  const { type, value } = item ?? {};
465
- if (type !== "Url") {
562
+ if (type !== AST_TYPES.URL) {
466
563
  return;
467
564
  }
468
565
  const str = value.replace(/\\\\/g, "\\").replaceAll('"', '\\"');
469
566
  return `url("${str}")`;
470
567
  };
471
568
 
472
- // Parse <string>.
473
- exports.parseString = (val) => {
569
+ /**
570
+ * Parses a <string> value.
571
+ *
572
+ * @param {Array<object>} val - The AST value.
573
+ * @returns {string|undefined} The parsed string.
574
+ */
575
+ const parseString = (val) => {
474
576
  const [item] = val;
475
577
  const { type, value } = item ?? {};
476
- if (type !== "String") {
578
+ if (type !== AST_TYPES.STRING) {
477
579
  return;
478
580
  }
479
581
  const str = value.replace(/\\\\/g, "\\").replaceAll('"', '\\"');
480
582
  return `"${str}"`;
481
583
  };
482
584
 
483
- // Parse <color>.
484
- exports.parseColor = (val) => {
585
+ /**
586
+ * Parses a <color> value.
587
+ *
588
+ * @param {Array<object>} val - The AST value.
589
+ * @returns {string|undefined} The parsed color.
590
+ */
591
+ const parseColor = (val) => {
485
592
  const [item] = val;
486
593
  const { name, type, value } = item ?? {};
487
594
  switch (type) {
488
- case "Function": {
595
+ case AST_TYPES.FUNCTION: {
489
596
  const res = resolveColor(`${name}(${value})`, {
490
597
  format: "specifiedValue"
491
598
  });
@@ -494,7 +601,7 @@ exports.parseColor = (val) => {
494
601
  }
495
602
  break;
496
603
  }
497
- case "Hash": {
604
+ case AST_TYPES.HASH: {
498
605
  const res = resolveColor(`#${value}`, {
499
606
  format: "specifiedValue"
500
607
  });
@@ -503,8 +610,8 @@ exports.parseColor = (val) => {
503
610
  }
504
611
  break;
505
612
  }
506
- case "Identifier": {
507
- if (SYS_COLOR.includes(name)) {
613
+ case AST_TYPES.IDENTIFIER: {
614
+ if (SYS_COLORS.has(name)) {
508
615
  return name;
509
616
  }
510
617
  const res = resolveColor(name, {
@@ -519,11 +626,16 @@ exports.parseColor = (val) => {
519
626
  }
520
627
  };
521
628
 
522
- // Parse <gradient>.
523
- exports.parseGradient = (val) => {
629
+ /**
630
+ * Parses a <gradient> value.
631
+ *
632
+ * @param {Array<object>} val - The AST value.
633
+ * @returns {string|undefined} The parsed gradient.
634
+ */
635
+ const parseGradient = (val) => {
524
636
  const [item] = val;
525
637
  const { name, type, value } = item ?? {};
526
- if (type !== "Function") {
638
+ if (type !== AST_TYPES.FUNCTION) {
527
639
  return;
528
640
  }
529
641
  const res = resolveGradient(`${name}(${value})`, {
@@ -533,3 +645,251 @@ exports.parseGradient = (val) => {
533
645
  return res;
534
646
  }
535
647
  };
648
+
649
+ /**
650
+ * Resolves a keyword value.
651
+ *
652
+ * @param {Array<object>} value - The AST node array containing the keyword value.
653
+ * @param {object} [opt={}] - The options for parsing.
654
+ * @returns {string|undefined} The resolved keyword or undefined.
655
+ */
656
+ const resolveKeywordValue = (value, opt = {}) => {
657
+ const [{ name, type }] = value;
658
+ const { length } = opt;
659
+ switch (type) {
660
+ case AST_TYPES.GLOBAL_KEYWORD: {
661
+ if (length > 1) {
662
+ return;
663
+ }
664
+ return name;
665
+ }
666
+ case AST_TYPES.IDENTIFIER: {
667
+ return name;
668
+ }
669
+ default:
670
+ }
671
+ };
672
+
673
+ /**
674
+ * Resolves a function value.
675
+ *
676
+ * @param {Array<object>} value - The AST node array containing the function value.
677
+ * @param {object} [opt={}] - The options for parsing.
678
+ * @returns {string|undefined} The resolved function or undefined.
679
+ */
680
+ const resolveFunctionValue = (value, opt = {}) => {
681
+ const [{ name, type, value: itemValue }] = value;
682
+ const { length } = opt;
683
+ switch (type) {
684
+ case AST_TYPES.FUNCTION: {
685
+ return `${name}(${itemValue})`;
686
+ }
687
+ case AST_TYPES.GLOBAL_KEYWORD: {
688
+ if (length > 1) {
689
+ return;
690
+ }
691
+ return name;
692
+ }
693
+ case AST_TYPES.IDENTIFIER: {
694
+ return name;
695
+ }
696
+ default:
697
+ }
698
+ };
699
+
700
+ /**
701
+ * Resolves a length or percentage or number value.
702
+ *
703
+ * @param {Array<object>} value - The AST node array containing the value.
704
+ * @param {object} [opt={}] - The options for parsing.
705
+ * @returns {string|undefined} The resolved length/percentage/number or undefined.
706
+ */
707
+ const resolveNumericValue = (value, opt = {}) => {
708
+ const [{ name, type: itemType, value: itemValue }] = value;
709
+ const { length, type } = opt;
710
+ switch (itemType) {
711
+ case AST_TYPES.CALC: {
712
+ return `${name}(${itemValue})`;
713
+ }
714
+ case AST_TYPES.DIMENSION: {
715
+ if (type === "angle") {
716
+ return parseAngle(value, opt);
717
+ }
718
+ return parseLength(value, opt);
719
+ }
720
+ case AST_TYPES.GLOBAL_KEYWORD: {
721
+ if (length > 1) {
722
+ return;
723
+ }
724
+ return name;
725
+ }
726
+ case AST_TYPES.IDENTIFIER: {
727
+ return name;
728
+ }
729
+ case AST_TYPES.NUMBER: {
730
+ switch (type) {
731
+ case "angle": {
732
+ return parseAngle(value, opt);
733
+ }
734
+ case "length": {
735
+ return parseLength(value, opt);
736
+ }
737
+ case "percentage": {
738
+ return parsePercentage(value, opt);
739
+ }
740
+ default: {
741
+ return parseNumber(value, opt);
742
+ }
743
+ }
744
+ }
745
+ case AST_TYPES.PERCENTAGE: {
746
+ return parsePercentage(value, opt);
747
+ }
748
+ default:
749
+ }
750
+ };
751
+
752
+ /**
753
+ * Resolves a color value.
754
+ *
755
+ * @param {Array<object>} value - The AST node array containing the color value.
756
+ * @param {object} [opt={}] - The options for parsing.
757
+ * @returns {string|undefined} The resolved color or undefined.
758
+ */
759
+ const resolveColorValue = (value, opt = {}) => {
760
+ const [{ name, type }] = value;
761
+ const { length } = opt;
762
+ switch (type) {
763
+ case AST_TYPES.GLOBAL_KEYWORD: {
764
+ if (length > 1) {
765
+ return;
766
+ }
767
+ return name;
768
+ }
769
+ default: {
770
+ return parseColor(value, opt);
771
+ }
772
+ }
773
+ };
774
+
775
+ /**
776
+ * Resolves a gradient or URL value.
777
+ *
778
+ * @param {Array<object>} value - The AST node array containing the color value.
779
+ * @param {object} [opt={}] - The options for parsing.
780
+ * @returns {string|undefined} The resolved gradient/url or undefined.
781
+ */
782
+ const resolveGradientUrlValue = (value, opt = {}) => {
783
+ const [{ name, type }] = value;
784
+ const { length } = opt;
785
+ switch (type) {
786
+ case AST_TYPES.GLOBAL_KEYWORD: {
787
+ if (length > 1) {
788
+ return;
789
+ }
790
+ return name;
791
+ }
792
+ case AST_TYPES.IDENTIFIER: {
793
+ return name;
794
+ }
795
+ case AST_TYPES.URL: {
796
+ return parseUrl(value, opt);
797
+ }
798
+ default: {
799
+ return parseGradient(value, opt);
800
+ }
801
+ }
802
+ };
803
+
804
+ /**
805
+ * Resolves a border shorthand value.
806
+ *
807
+ * @param {Array<object>} value - The AST node array containing the shorthand value.
808
+ * @param {object} subProps - The sub properties object.
809
+ * @param {Map} parsedValues - The Map of parsed values.
810
+ * @returns {Array|string|undefined} - The resolved [prop, value] pair, keyword or undefined.
811
+ */
812
+ const resolveBorderShorthandValue = (value, subProps, parsedValues) => {
813
+ const [{ isNumber, name, type, value: itemValue }] = value;
814
+ const { color: colorProp, style: styleProp, width: widthProp } = subProps;
815
+ switch (type) {
816
+ case AST_TYPES.CALC: {
817
+ if (isNumber || parsedValues.has(widthProp)) {
818
+ return;
819
+ }
820
+ return [widthProp, `${name}(${itemValue}`];
821
+ }
822
+ case AST_TYPES.DIMENSION:
823
+ case AST_TYPES.NUMBER: {
824
+ if (parsedValues.has(widthProp)) {
825
+ return;
826
+ }
827
+ const parsedValue = parseLength(value, { min: 0 });
828
+ if (!parsedValue) {
829
+ return;
830
+ }
831
+ return [widthProp, parsedValue];
832
+ }
833
+ case AST_TYPES.FUNCTION:
834
+ case AST_TYPES.HASH: {
835
+ if (parsedValues.has(colorProp)) {
836
+ return;
837
+ }
838
+ const parsedValue = parseColor(value);
839
+ if (!parsedValue) {
840
+ return;
841
+ }
842
+ return [colorProp, parsedValue];
843
+ }
844
+ case AST_TYPES.GLOBAL_KEYWORD: {
845
+ return name;
846
+ }
847
+ case AST_TYPES.IDENTIFIER: {
848
+ if (isValidPropertyValue(widthProp, name)) {
849
+ if (parsedValues.has(widthProp)) {
850
+ return;
851
+ }
852
+ return [widthProp, name];
853
+ } else if (isValidPropertyValue(styleProp, name)) {
854
+ if (parsedValues.has(styleProp)) {
855
+ return;
856
+ }
857
+ return [styleProp, name];
858
+ } else if (isValidPropertyValue(colorProp, name)) {
859
+ if (parsedValues.has(colorProp)) {
860
+ return;
861
+ }
862
+ return [colorProp, name];
863
+ }
864
+ break;
865
+ }
866
+ default:
867
+ }
868
+ };
869
+
870
+ module.exports = {
871
+ AST_TYPES,
872
+ hasCalcFunc,
873
+ hasVarFunc,
874
+ isGlobalKeyword,
875
+ isValidPropertyValue,
876
+ parseAngle,
877
+ parseCSS,
878
+ parseColor,
879
+ parseGradient,
880
+ parseLength,
881
+ parseNumber,
882
+ parsePercentage,
883
+ parsePropertyValue,
884
+ parseString,
885
+ parseUrl,
886
+ prepareValue,
887
+ resolveBorderShorthandValue,
888
+ resolveCalc,
889
+ resolveColorValue,
890
+ resolveFunctionValue,
891
+ resolveGradientUrlValue,
892
+ resolveKeywordValue,
893
+ resolveNumericValue,
894
+ splitValue
895
+ };