cssstyle 5.2.0 → 5.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 (88) hide show
  1. package/lib/CSSStyleDeclaration.js +250 -254
  2. package/lib/generated/allProperties.js +39 -1
  3. package/lib/generated/implementedProperties.js +2219 -80
  4. package/lib/generated/properties.js +5253 -1904
  5. package/lib/normalize.js +1417 -0
  6. package/lib/parsers.js +372 -389
  7. package/lib/properties/background.js +76 -63
  8. package/lib/properties/backgroundAttachment.js +37 -22
  9. package/lib/properties/backgroundClip.js +37 -22
  10. package/lib/properties/backgroundColor.js +35 -15
  11. package/lib/properties/backgroundImage.js +49 -19
  12. package/lib/properties/backgroundOrigin.js +37 -22
  13. package/lib/properties/backgroundPosition.js +145 -128
  14. package/lib/properties/backgroundRepeat.js +55 -48
  15. package/lib/properties/backgroundSize.js +86 -46
  16. package/lib/properties/border.js +139 -22
  17. package/lib/properties/borderBottom.js +137 -21
  18. package/lib/properties/borderBottomColor.js +41 -16
  19. package/lib/properties/borderBottomStyle.js +39 -30
  20. package/lib/properties/borderBottomWidth.js +49 -16
  21. package/lib/properties/borderCollapse.js +32 -8
  22. package/lib/properties/borderColor.js +96 -23
  23. package/lib/properties/borderLeft.js +137 -21
  24. package/lib/properties/borderLeftColor.js +41 -16
  25. package/lib/properties/borderLeftStyle.js +39 -30
  26. package/lib/properties/borderLeftWidth.js +49 -16
  27. package/lib/properties/borderRight.js +137 -21
  28. package/lib/properties/borderRightColor.js +41 -16
  29. package/lib/properties/borderRightStyle.js +39 -30
  30. package/lib/properties/borderRightWidth.js +49 -16
  31. package/lib/properties/borderSpacing.js +42 -25
  32. package/lib/properties/borderStyle.js +96 -34
  33. package/lib/properties/borderTop.js +131 -15
  34. package/lib/properties/borderTopColor.js +41 -16
  35. package/lib/properties/borderTopStyle.js +39 -30
  36. package/lib/properties/borderTopWidth.js +49 -16
  37. package/lib/properties/borderWidth.js +108 -23
  38. package/lib/properties/bottom.js +40 -12
  39. package/lib/properties/clear.js +32 -22
  40. package/lib/properties/clip.js +46 -32
  41. package/lib/properties/color.js +34 -13
  42. package/lib/properties/display.js +169 -179
  43. package/lib/properties/flex.js +137 -38
  44. package/lib/properties/flexBasis.js +43 -14
  45. package/lib/properties/flexGrow.js +42 -9
  46. package/lib/properties/flexShrink.js +42 -9
  47. package/lib/properties/float.js +32 -9
  48. package/lib/properties/floodColor.js +34 -13
  49. package/lib/properties/font.js +145 -44
  50. package/lib/properties/fontFamily.js +66 -67
  51. package/lib/properties/fontSize.js +43 -26
  52. package/lib/properties/fontStyle.js +42 -11
  53. package/lib/properties/fontVariant.js +52 -15
  54. package/lib/properties/fontWeight.js +47 -15
  55. package/lib/properties/height.js +40 -13
  56. package/lib/properties/left.js +40 -12
  57. package/lib/properties/lightingColor.js +34 -13
  58. package/lib/properties/lineHeight.js +45 -18
  59. package/lib/properties/margin.js +73 -36
  60. package/lib/properties/marginBottom.js +43 -19
  61. package/lib/properties/marginLeft.js +43 -19
  62. package/lib/properties/marginRight.js +43 -19
  63. package/lib/properties/marginTop.js +43 -19
  64. package/lib/properties/opacity.js +41 -28
  65. package/lib/properties/outlineColor.js +34 -13
  66. package/lib/properties/padding.js +71 -36
  67. package/lib/properties/paddingBottom.js +44 -21
  68. package/lib/properties/paddingLeft.js +44 -19
  69. package/lib/properties/paddingRight.js +44 -19
  70. package/lib/properties/paddingTop.js +44 -19
  71. package/lib/properties/right.js +40 -12
  72. package/lib/properties/stopColor.js +34 -13
  73. package/lib/properties/top.js +40 -12
  74. package/lib/properties/webkitBorderAfterColor.js +34 -13
  75. package/lib/properties/webkitBorderBeforeColor.js +34 -13
  76. package/lib/properties/webkitBorderEndColor.js +34 -13
  77. package/lib/properties/webkitBorderStartColor.js +34 -13
  78. package/lib/properties/webkitColumnRuleColor.js +34 -13
  79. package/lib/properties/webkitTapHighlightColor.js +34 -13
  80. package/lib/properties/webkitTextEmphasisColor.js +34 -13
  81. package/lib/properties/webkitTextFillColor.js +34 -13
  82. package/lib/properties/webkitTextStrokeColor.js +34 -13
  83. package/lib/properties/width.js +40 -13
  84. package/lib/{allWebkitProperties.js → utils/allExtraProperties.js} +42 -1
  85. package/lib/utils/propertyDescriptors.js +6 -3
  86. package/package.json +11 -10
  87. package/lib/allExtraProperties.js +0 -49
  88. package/lib/shorthandProperties.js +0 -21
package/lib/parsers.js CHANGED
@@ -1,31 +1,16 @@
1
- /**
2
- * These are commonly used parsers for CSS Values they take a string to parse
3
- * and return a string after it's been converted, if needed
4
- */
5
1
  "use strict";
6
2
 
7
3
  const {
8
4
  resolve: resolveColor,
9
- utils: { cssCalc, isColor, resolveGradient, splitValue }
5
+ utils: { cssCalc, resolveGradient, splitValue }
10
6
  } = require("@asamuzakjp/css-color");
11
7
  const { next: syntaxes } = require("@csstools/css-syntax-patches-for-csstree");
12
8
  const csstree = require("css-tree");
13
9
  const { asciiLowercase } = require("./utils/strings");
14
10
 
15
- // CSS global values
11
+ // CSS global keywords
16
12
  // @see https://drafts.csswg.org/css-cascade-5/#defaulting-keywords
17
- const GLOBAL_VALUE = Object.freeze(["initial", "inherit", "unset", "revert", "revert-layer"]);
18
-
19
- // Numeric data types
20
- const NUM_TYPE = Object.freeze({
21
- UNDEFINED: 0,
22
- VAR: 1,
23
- NUMBER: 2,
24
- PERCENT: 4,
25
- LENGTH: 8,
26
- ANGLE: 0x10,
27
- CALC: 0x20
28
- });
13
+ const GLOBAL_KEY = Object.freeze(["initial", "inherit", "unset", "revert", "revert-layer"]);
29
14
 
30
15
  // System colors
31
16
  // @see https://drafts.csswg.org/css-color/#css-system-colors
@@ -76,49 +61,19 @@ const SYS_COLOR = Object.freeze([
76
61
  ]);
77
62
 
78
63
  // Regular expressions
79
- const DIGIT = "(?:0|[1-9]\\d*)";
80
- const NUMBER = `[+-]?(?:${DIGIT}(?:\\.\\d*)?|\\.\\d+)(?:e-?${DIGIT})?`;
81
- const unitRegEx = new RegExp(`^(${NUMBER})([a-z]+|%)?$`, "i");
82
- const urlRegEx = /^url\(\s*((?:[^)]|\\\))*)\s*\)$/;
83
- const keywordRegEx = /^[a-z]+(?:-[a-z]+)*$/i;
84
- const stringRegEx = /^("[^"]*"|'[^']*')$/;
64
+ const CALC_FUNC_NAMES =
65
+ "(?:a?(?:cos|sin|tan)|abs|atan2|calc|clamp|exp|hypot|log|max|min|mod|pow|rem|round|sign|sqrt)";
66
+ const calcRegEx = new RegExp(`^${CALC_FUNC_NAMES}\\(`);
67
+ const calcContainedRegEx = new RegExp(`(?<=[*/\\s(])${CALC_FUNC_NAMES}\\(`);
68
+ const calcNameRegEx = new RegExp(`^${CALC_FUNC_NAMES}$`);
85
69
  const varRegEx = /^var\(/;
86
70
  const varContainedRegEx = /(?<=[*/\s(])var\(/;
87
- const calcRegEx =
88
- /^(?:a?(?:cos|sin|tan)|abs|atan2|calc|clamp|exp|hypot|log|max|min|mod|pow|rem|round|sign|sqrt)\(/;
89
- const gradientRegEx = /^(?:repeating-)?(?:conic|linear|radial)-gradient\(/;
90
- const functionRegEx = /^([a-z][a-z\d]*(?:-[a-z\d]+)*)\(/i;
91
71
 
92
- const getNumericType = function getNumericType(val) {
93
- if (varRegEx.test(val)) {
94
- return NUM_TYPE.VAR;
95
- }
96
- if (calcRegEx.test(val)) {
97
- return NUM_TYPE.CALC;
98
- }
99
- if (unitRegEx.test(val)) {
100
- const [, , unit] = unitRegEx.exec(val);
101
- if (!unit) {
102
- return NUM_TYPE.NUMBER;
103
- }
104
- if (unit === "%") {
105
- return NUM_TYPE.PERCENT;
106
- }
107
- if (/^(?:[cm]m|[dls]?v(?:[bhiw]|max|min)|in|p[ctx]|q|r?(?:[cl]h|cap|e[mx]|ic))$/i.test(unit)) {
108
- return NUM_TYPE.LENGTH;
109
- }
110
- if (/^(?:deg|g?rad|turn)$/i.test(unit)) {
111
- return NUM_TYPE.ANGLE;
112
- }
113
- }
114
- return NUM_TYPE.UNDEFINED;
115
- };
116
-
117
- // Patch css-tree
72
+ // Patched css-tree
118
73
  const cssTree = csstree.fork(syntaxes);
119
74
 
120
75
  // Prepare stringified value.
121
- exports.prepareValue = function prepareValue(value, globalObject = globalThis) {
76
+ exports.prepareValue = (value, globalObject = globalThis) => {
122
77
  // `null` is converted to an empty string.
123
78
  // @see https://webidl.spec.whatwg.org/#LegacyNullToEmptyString
124
79
  if (value === null) {
@@ -137,416 +92,444 @@ exports.prepareValue = function prepareValue(value, globalObject = globalThis) {
137
92
  default: {
138
93
  const str = value.toString();
139
94
  if (typeof str === "string") {
140
- return str;
95
+ return str.trim();
141
96
  }
142
97
  throw new globalObject.TypeError(`Can not convert ${type} to string.`);
143
98
  }
144
99
  }
145
100
  };
146
101
 
147
- exports.hasVarFunc = function hasVarFunc(val) {
102
+ // Value is a global keyword.
103
+ exports.isGlobalKeyword = (val) => {
104
+ return GLOBAL_KEY.includes(asciiLowercase(val));
105
+ };
106
+
107
+ // Value starts with and/or contains CSS var() function.
108
+ exports.hasVarFunc = (val) => {
148
109
  return varRegEx.test(val) || varContainedRegEx.test(val);
149
110
  };
150
111
 
151
- exports.parseNumber = function parseNumber(val, restrictToPositive = false) {
152
- if (val === "") {
153
- return "";
112
+ // Value starts with and/or contains CSS calc() related functions.
113
+ exports.hasCalcFunc = (val) => {
114
+ return calcRegEx.test(val) || calcContainedRegEx.test(val);
115
+ };
116
+
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);
154
125
  }
155
- const type = getNumericType(val);
156
- switch (type) {
157
- case NUM_TYPE.VAR:
158
- return val;
159
- case NUM_TYPE.CALC:
160
- return cssCalc(val, {
161
- format: "specifiedValue"
162
- });
163
- case NUM_TYPE.NUMBER: {
164
- const num = parseFloat(val);
165
- if (restrictToPositive && num < 0) {
166
- return;
167
- }
168
- return `${num}`;
169
- }
170
- default:
171
- if (varContainedRegEx.test(val)) {
172
- return val;
173
- }
126
+ const ast = cssTree.parse(val, opt);
127
+ if (toObject) {
128
+ return cssTree.toPlainObject(ast);
174
129
  }
130
+ return ast;
175
131
  };
176
132
 
177
- exports.parseLength = function parseLength(val, restrictToPositive = false) {
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
+ }
178
139
  if (val === "") {
179
- return "";
140
+ return true;
180
141
  }
181
- const type = getNumericType(val);
182
- switch (type) {
183
- case NUM_TYPE.VAR:
184
- return val;
185
- case NUM_TYPE.CALC:
186
- return cssCalc(val, {
187
- format: "specifiedValue"
188
- });
189
- case NUM_TYPE.NUMBER:
190
- if (parseFloat(val) === 0) {
191
- return "0px";
192
- }
193
- return;
194
- case NUM_TYPE.LENGTH: {
195
- const [, numVal, unit] = unitRegEx.exec(val);
196
- const num = parseFloat(numVal);
197
- if (restrictToPositive && num < 0) {
198
- return;
199
- }
200
- return `${num}${asciiLowercase(unit)}`;
142
+ // cssTree.lexer does not support deprecated system colors
143
+ // @see https://github.com/w3c/webref/issues/1519#issuecomment-3120290261
144
+ // @see https://github.com/w3c/webref/issues/1647
145
+ if (SYS_COLOR.includes(asciiLowercase(val))) {
146
+ if (/^(?:-webkit-)?(?:[a-z][a-z\d]*-)*color$/i.test(prop)) {
147
+ return true;
201
148
  }
202
- default:
203
- if (varContainedRegEx.test(val)) {
204
- return val;
205
- }
149
+ return false;
206
150
  }
151
+ let ast;
152
+ try {
153
+ ast = exports.parseCSS(val, {
154
+ context: "value"
155
+ });
156
+ } catch {
157
+ return false;
158
+ }
159
+ const { error, matched } = cssTree.lexer.matchProperty(prop, ast);
160
+ return error === null && matched !== null;
207
161
  };
208
162
 
209
- exports.parsePercent = function parsePercent(val, restrictToPositive = false) {
210
- if (val === "") {
211
- return "";
163
+ // Simplify / resolve math functions.
164
+ exports.resolveCalc = (val, opt = { format: "specifiedValue" }) => {
165
+ if (typeof val !== "string") {
166
+ val = exports.prepareValue(val);
212
167
  }
213
- const type = getNumericType(val);
214
- switch (type) {
215
- case NUM_TYPE.VAR:
216
- return val;
217
- case NUM_TYPE.CALC:
218
- return cssCalc(val, {
219
- format: "specifiedValue"
220
- });
221
- case NUM_TYPE.NUMBER:
222
- if (parseFloat(val) === 0) {
223
- return "0%";
224
- }
225
- return;
226
- case NUM_TYPE.PERCENT: {
227
- const [, numVal, unit] = unitRegEx.exec(val);
228
- const num = parseFloat(numVal);
229
- if (restrictToPositive && num < 0) {
230
- return;
168
+ if (val === "" || exports.hasVarFunc(val) || !exports.hasCalcFunc(val)) {
169
+ return val;
170
+ }
171
+ const obj = exports.parseCSS(val, { context: "value" }, true);
172
+ if (!obj?.children) {
173
+ return;
174
+ }
175
+ const { children: items } = obj;
176
+ const values = [];
177
+ for (const item of items) {
178
+ const { type: itemType, name: itemName, value: itemValue } = item;
179
+ if (itemType === "Function") {
180
+ const value = cssTree
181
+ .generate(item)
182
+ .replace(/\)(?!\)|\s|,)/g, ") ")
183
+ .trim();
184
+ if (calcNameRegEx.test(itemName)) {
185
+ const newValue = cssCalc(value, opt);
186
+ values.push(newValue);
187
+ } else {
188
+ values.push(value);
231
189
  }
232
- return `${num}${asciiLowercase(unit)}`;
190
+ } else if (itemType === "String") {
191
+ values.push(`"${itemValue}"`);
192
+ } else {
193
+ values.push(itemName ?? itemValue);
233
194
  }
234
- default:
235
- if (varContainedRegEx.test(val)) {
236
- return val;
237
- }
238
195
  }
196
+ return values.join(" ");
239
197
  };
240
198
 
241
- // Either a length or a percent.
242
- exports.parseMeasurement = function parseMeasurement(val, restrictToPositive = false) {
243
- if (val === "") {
244
- return "";
245
- }
246
- const type = getNumericType(val);
247
- switch (type) {
248
- case NUM_TYPE.VAR:
249
- return val;
250
- case NUM_TYPE.CALC:
251
- return cssCalc(val, {
252
- format: "specifiedValue"
253
- });
254
- case NUM_TYPE.NUMBER:
255
- if (parseFloat(val) === 0) {
256
- return "0px";
257
- }
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)) {
204
+ return val;
205
+ } else if (exports.hasCalcFunc(val)) {
206
+ const calculatedValue = exports.resolveCalc(val, {
207
+ format: "specifiedValue"
208
+ });
209
+ if (typeof calculatedValue !== "string") {
258
210
  return;
259
- case NUM_TYPE.LENGTH:
260
- case NUM_TYPE.PERCENT: {
261
- const [, numVal, unit] = unitRegEx.exec(val);
262
- const num = parseFloat(numVal);
263
- if (restrictToPositive && num < 0) {
264
- return;
265
- }
266
- return `${num}${asciiLowercase(unit)}`;
267
211
  }
268
- default:
269
- if (varContainedRegEx.test(val)) {
270
- return val;
212
+ val = calculatedValue;
213
+ }
214
+ const lowerCasedValue = asciiLowercase(val);
215
+ if (GLOBAL_KEY.includes(lowerCasedValue)) {
216
+ if (inArray) {
217
+ return [
218
+ {
219
+ type: "GlobalKeyword",
220
+ name: lowerCasedValue
221
+ }
222
+ ];
223
+ }
224
+ return lowerCasedValue;
225
+ } else if (SYS_COLOR.includes(lowerCasedValue)) {
226
+ if (/^(?:(?:-webkit-)?(?:[a-z][a-z\d]*-)*color|border)$/i.test(prop)) {
227
+ if (inArray) {
228
+ return [
229
+ {
230
+ type: "Identifier",
231
+ name: lowerCasedValue
232
+ }
233
+ ];
271
234
  }
235
+ return lowerCasedValue;
236
+ }
237
+ return;
272
238
  }
273
- };
274
-
275
- exports.parseAngle = function parseAngle(val, normalizeDeg = false) {
276
- if (val === "") {
277
- return "";
278
- }
279
- const type = getNumericType(val);
280
- switch (type) {
281
- case NUM_TYPE.VAR:
282
- return val;
283
- case NUM_TYPE.CALC:
284
- return cssCalc(val, {
285
- format: "specifiedValue"
286
- });
287
- case NUM_TYPE.NUMBER:
288
- if (parseFloat(val) === 0) {
289
- return "0deg";
290
- }
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) {
291
245
  return;
292
- case NUM_TYPE.ANGLE: {
293
- let [, numVal, unit] = unitRegEx.exec(val);
294
- numVal = parseFloat(numVal);
295
- unit = asciiLowercase(unit);
296
- if (unit === "deg") {
297
- if (normalizeDeg && numVal < 0) {
298
- while (numVal < 0) {
299
- numVal += 360;
246
+ }
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
+ });
283
+ } else {
284
+ parsedValues.push({
285
+ type: "Calc",
286
+ name: "calc",
287
+ isNumber: false,
288
+ value: `${asciiLowercase(itemValue)}`,
289
+ raw
290
+ });
291
+ }
292
+ } else {
293
+ parsedValues.push({
294
+ type: "Calc",
295
+ name: "calc",
296
+ isNumber: false,
297
+ value: asciiLowercase(itemValue),
298
+ raw
299
+ });
300
+ }
301
+ } else {
302
+ parsedValues.push({
303
+ type,
304
+ name,
305
+ value: asciiLowercase(itemValue),
306
+ raw
307
+ });
308
+ }
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
+ });
319
+ }
320
+ break;
321
+ }
322
+ default: {
323
+ parsedValues.push(item);
300
324
  }
301
325
  }
302
- numVal %= 360;
303
326
  }
304
- return `${numVal}${unit}`;
327
+ return parsedValues;
305
328
  }
306
- default:
307
- if (varContainedRegEx.test(val)) {
308
- return val;
309
- }
329
+ } catch {
330
+ return;
310
331
  }
332
+ return val;
311
333
  };
312
334
 
313
- exports.parseUrl = function parseUrl(val) {
314
- if (val === "") {
315
- return val;
316
- }
317
- const res = urlRegEx.exec(val);
318
- if (!res) {
319
- return;
320
- }
321
- let str = res[1];
322
- // If it starts with single or double quotes, does it end with the same?
323
- if ((str[0] === '"' || str[0] === "'") && str[0] !== str[str.length - 1]) {
335
+ // Parse <number>.
336
+ exports.parseNumber = (val, opt = {}) => {
337
+ const [item] = val;
338
+ const { type, value } = item ?? {};
339
+ if (type !== "Number") {
324
340
  return;
325
341
  }
326
- if (str[0] === '"' || str[0] === "'") {
327
- str = str.substr(1, str.length - 2);
328
- }
329
- let urlstr = "";
330
- let escaped = false;
331
- for (let i = 0; i < str.length; i++) {
332
- switch (str[i]) {
333
- case "\\":
334
- if (escaped) {
335
- urlstr += "\\\\";
336
- escaped = false;
337
- } else {
338
- escaped = true;
339
- }
340
- break;
341
- case "(":
342
- case ")":
343
- case " ":
344
- case "\t":
345
- case "\n":
346
- case "'":
347
- if (!escaped) {
348
- return;
349
- }
350
- urlstr += str[i];
351
- escaped = false;
352
- break;
353
- case '"':
354
- if (!escaped) {
355
- return;
356
- }
357
- urlstr += '\\"';
358
- escaped = false;
359
- break;
360
- default:
361
- urlstr += str[i];
362
- escaped = false;
342
+ const { clamp } = opt;
343
+ const max = opt.max ?? Number.INFINITY;
344
+ const min = opt.min ?? Number.NEGATIVE_INFINITY;
345
+ let num = parseFloat(value);
346
+ if (clamp) {
347
+ if (num > max) {
348
+ num = max;
349
+ } else if (num < min) {
350
+ num = min;
363
351
  }
352
+ } else if (num > max || num < min) {
353
+ return;
364
354
  }
365
- return `url("${urlstr}")`;
355
+ return `${num}`;
366
356
  };
367
357
 
368
- exports.parseString = function parseString(val) {
369
- if (val === "") {
370
- return "";
371
- }
372
- if (!stringRegEx.test(val)) {
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")) {
373
363
  return;
374
364
  }
375
- val = val.substr(1, val.length - 2);
376
- let str = "";
377
- let escaped = false;
378
- for (let i = 0; i < val.length; i++) {
379
- switch (val[i]) {
380
- case "\\":
381
- if (escaped) {
382
- str += "\\\\";
383
- escaped = false;
384
- } else {
385
- escaped = true;
386
- }
387
- break;
388
- case '"':
389
- str += '\\"';
390
- escaped = false;
391
- break;
392
- default:
393
- str += val[i];
394
- escaped = false;
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;
395
374
  }
375
+ } else if (num > max || num < min) {
376
+ return;
377
+ }
378
+ if (num === 0 && !unit) {
379
+ return `${num}px`;
380
+ } else if (unit) {
381
+ return `${num}${asciiLowercase(unit)}`;
396
382
  }
397
- return `"${str}"`;
398
383
  };
399
384
 
400
- exports.parseKeyword = function parseKeyword(val, validKeywords = []) {
401
- if (val === "") {
402
- return "";
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;
403
391
  }
404
- if (varRegEx.test(val)) {
405
- return val;
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;
406
404
  }
407
- val = asciiLowercase(val);
408
- if (validKeywords.includes(val) || GLOBAL_VALUE.includes(val)) {
409
- return val;
405
+ if (num === 0) {
406
+ return `${num}%`;
410
407
  }
408
+ return `${num}%`;
411
409
  };
412
410
 
413
- exports.parseColor = function parseColor(val) {
414
- if (val === "") {
415
- return "";
416
- }
417
- if (varRegEx.test(val)) {
418
- return val;
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;
419
417
  }
420
- if (/^[a-z]+$/i.test(val)) {
421
- const v = asciiLowercase(val);
422
- if (SYS_COLOR.includes(v)) {
423
- return v;
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;
424
427
  }
428
+ } else if (num > max || num < min) {
429
+ return;
425
430
  }
426
- const res = resolveColor(val, {
427
- format: "specifiedValue"
428
- });
429
- if (res) {
430
- return res;
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`;
431
440
  }
432
- return exports.parseKeyword(val);
433
441
  };
434
442
 
435
- exports.parseImage = function parseImage(val) {
436
- if (val === "") {
437
- return "";
438
- }
439
- if (gradientRegEx.test(val) && exports.hasVarFunc(val)) {
440
- return val;
441
- }
442
- if (keywordRegEx.test(val)) {
443
- return exports.parseKeyword(val, ["none"]);
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;
444
449
  }
445
- const res = resolveGradient(val, {
446
- format: "specifiedValue"
447
- });
448
- if (res) {
449
- return res;
450
+ const num = parseFloat(value);
451
+ if (unit) {
452
+ if (!/^(?:deg|g?rad|turn)$/i.test(unit)) {
453
+ return;
454
+ }
455
+ return `${num}${asciiLowercase(unit)}`;
456
+ } else if (num === 0) {
457
+ return `${num}deg`;
450
458
  }
451
- return exports.parseUrl(val);
452
459
  };
453
460
 
454
- exports.parseFunction = function parseFunction(val) {
455
- if (val === "") {
456
- return {
457
- name: null,
458
- value: ""
459
- };
460
- }
461
- if (functionRegEx.test(val) && val.endsWith(")")) {
462
- if (varRegEx.test(val) || varContainedRegEx.test(val)) {
463
- return {
464
- name: "var",
465
- value: val
466
- };
467
- }
468
- const [, name] = functionRegEx.exec(val);
469
- const value = val
470
- .replace(new RegExp(`^${name}\\(`), "")
471
- .replace(/\)$/, "")
472
- .trim();
473
- return {
474
- name,
475
- value
476
- };
461
+ // Parse <url>.
462
+ exports.parseUrl = (val) => {
463
+ const [item] = val;
464
+ const { type, value } = item ?? {};
465
+ if (type !== "Url") {
466
+ return;
477
467
  }
468
+ const str = value.replace(/\\\\/g, "\\").replaceAll('"', '\\"');
469
+ return `url("${str}")`;
478
470
  };
479
471
 
480
- exports.parseShorthand = function parseShorthand(val, shorthandFor, preserve = false) {
481
- const obj = {};
482
- if (val === "" || exports.hasVarFunc(val)) {
483
- for (const [property] of shorthandFor) {
484
- obj[property] = "";
485
- }
486
- return obj;
487
- }
488
- const key = exports.parseKeyword(val);
489
- if (key) {
490
- if (key === "inherit") {
491
- return obj;
492
- }
472
+ // Parse <string>.
473
+ exports.parseString = (val) => {
474
+ const [item] = val;
475
+ const { type, value } = item ?? {};
476
+ if (type !== "String") {
493
477
  return;
494
478
  }
495
- const parts = splitValue(val);
496
- const shorthandArr = [...shorthandFor];
497
- for (const part of parts) {
498
- let partValid = false;
499
- for (let i = 0; i < shorthandArr.length; i++) {
500
- const [property, value] = shorthandArr[i];
501
- if (value.isValid(part)) {
502
- partValid = true;
503
- obj[property] = value.parse(part);
504
- if (!preserve) {
505
- shorthandArr.splice(i, 1);
506
- break;
507
- }
479
+ const str = value.replace(/\\\\/g, "\\").replaceAll('"', '\\"');
480
+ return `"${str}"`;
481
+ };
482
+
483
+ // Parse <color>.
484
+ exports.parseColor = (val) => {
485
+ const [item] = val;
486
+ const { name, type, value } = item ?? {};
487
+ switch (type) {
488
+ case "Function": {
489
+ const res = resolveColor(`${name}(${value})`, {
490
+ format: "specifiedValue"
491
+ });
492
+ if (res) {
493
+ return res;
508
494
  }
495
+ break;
509
496
  }
510
- if (!partValid) {
511
- return;
497
+ case "Hash": {
498
+ const res = resolveColor(`#${value}`, {
499
+ format: "specifiedValue"
500
+ });
501
+ if (res) {
502
+ return res;
503
+ }
504
+ break;
512
505
  }
506
+ case "Identifier": {
507
+ if (SYS_COLOR.includes(name)) {
508
+ return name;
509
+ }
510
+ const res = resolveColor(name, {
511
+ format: "specifiedValue"
512
+ });
513
+ if (res) {
514
+ return res;
515
+ }
516
+ break;
517
+ }
518
+ default:
513
519
  }
514
- return obj;
515
- };
516
-
517
- exports.parseCSS = function parseCSS(val, opt, toObject = false) {
518
- const ast = cssTree.parse(val, opt);
519
- if (toObject) {
520
- return cssTree.toPlainObject(ast);
521
- }
522
- return ast;
523
520
  };
524
521
 
525
- // Returns `false` for custom property and/or var().
526
- exports.isValidPropertyValue = function isValidPropertyValue(prop, val) {
527
- // cssTree.lexer does not support deprecated system colors
528
- // @see https://github.com/w3c/webref/issues/1519#issuecomment-3120290261
529
- if (SYS_COLOR.includes(asciiLowercase(val))) {
530
- if (/^(?:-webkit-)?(?:[a-z][a-z\d]*-)*color$/i.test(prop)) {
531
- return true;
532
- }
533
- return false;
522
+ // Parse <gradient>.
523
+ exports.parseGradient = (val) => {
524
+ const [item] = val;
525
+ const { name, type, value } = item ?? {};
526
+ if (type !== "Function") {
527
+ return;
534
528
  }
535
- const ast = exports.parseCSS(val, {
536
- context: "value"
529
+ const res = resolveGradient(`${name}(${value})`, {
530
+ format: "specifiedValue"
537
531
  });
538
- const { error, matched } = cssTree.lexer.matchProperty(prop, ast);
539
- return error === null && matched !== null;
540
- };
541
-
542
- // Returns `false` for global values, e.g. "inherit".
543
- exports.isValidColor = function isValidColor(val) {
544
- if (SYS_COLOR.includes(asciiLowercase(val))) {
545
- return true;
532
+ if (res) {
533
+ return res;
546
534
  }
547
- return isColor(val);
548
535
  };
549
-
550
- // Splits value into an array.
551
- // @see https://github.com/asamuzaK/cssColor/blob/main/src/js/util.ts
552
- exports.splitValue = splitValue;