cssstyle 5.3.5 → 5.3.7

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