diff --git a/CHANGELOG.md b/CHANGELOG.md index a6885b1e2..3c93e9eae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,108 @@ # Changelog +# v1.6.6 + +## Fixes + +- [x] parsing aspect ratio using a single number +- [x] bugs in calc() +- [x] computing media range in container at-rule + +```css + + @media (aspect-ratio: 1.77) { + html { + background-color: cyan; + } +} + +body { + left: sin(45deg); + left: cos(45deg); + left: tan(45deg); + left: asin(0.5); + left: acos(0.5); + left: atan(10); + left: atan2(-1, 1); +} + +p { + font-size: calc(pow(10, 12) * 1rem); + font-size: calc(sqrt(100) * 1rem); + font-size: calc(hypot(3, 4) * 1rem); + font-size: calc(log(10) * 1rem); + font-size: calc(exp(10) * 1rem); +} + + +p { + color: alpha(from red / calc(alpha * 0.5)); +} +p { + font-size: calc(pow(e, pi) * 1rem); +} + +@container (min-width: 700px) { + .container { + +display: grid; + } +} + +@container (max-width: 1400px) { + .container { + +display: grid; + } +} + +@container (min-width: 700px) and (max-width: 1400px) { + .container { + +display: grid; + /* styles applied when a container is at least 700px */ + } +} + +``` +is now computed as + +```css +@media (aspect-ratio:1.77) { + html { + background-color: cyan + } +} +body { + left: .707107; + left: .707107; + left: 1; + left: 30deg; + left: 60deg; + left: .234turn; + left: -45deg +} +p { + font-size: 23.140693rem; + color: #ff000080 +} +@container (width>=700px) { + .container { + display: grid + } +} +@container (width<=1400px) { + .container { + display: grid + } +} +@container (700px<=width<=1400px) { + .container { + display: grid + } +} +``` + # v1.6.5 ## New diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..25264593d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,119 @@ +# Contributing + +Thanks for your interest in contributing to CSS Parser. This guide explains how to get the project running locally, update its generated configuration data, run the checks, and submit a change. It is intended to help new contributors get started and make contributions easier to review. + +## Get the source + +1. Fork the repository on GitHub. +2. Clone your fork and enter the project directory: + + ```sh + git clone https://github.com//css-parser.git + cd css-parser + ``` + +3. Add the upstream repository and install the root project dependencies: + + ```sh + git remote add upstream https://github.com/tbela99/css-parser.git + npm install + ``` + +4. Create a branch for your change: + + ```sh + git switch -c descriptive-branch-name + ``` + +Use a current Node.js release compatible with the project dependencies. + +## Update generated configuration + +The repository includes scripts for refreshing generated CSS property and syntax data. Run them from the project root: + +```sh +npm run config-update +npm run syntax-update +``` + +`config-update` regenerates `src/data/properties.json`. `syntax-update` regenerates `src/data/validation.json` from upstream CSS data and therefore requires an internet connection. Review the generated changes and include them when they are relevant to your contribution. + +## Building + +Run the build command: + +```sh +npm run build +``` + +Validate your changes before committing using deno: + +```sh +deno publish --dry-run +``` + +## Run tests + +Run the complete Node.js and browser test suites before submitting a change: + +```sh +npm test +``` + +For a quicker, focused check, run either suite: + +```sh +npm run test:node +npm run test:web +``` + +The browser tests use Playwright with Chromium, Firefox, and WebKit. Install the required browsers if Playwright reports that they are missing. + +For debugging, run either suite: + +```sh +npm run debug:node +npm run debug:web +``` +## Check code coverage + +Generate Node.js coverage reports with: + +```sh +npm run test:cov +``` + +The command prints a text summary and writes an HTML report under `coverage/`. Browser coverage can be collected separately with: + +```sh +npm run test:web-cov +``` + +When changing behavior, add or update tests that exercise it and use the coverage reports to spot untested paths. + +## Run the benchmark + +The benchmark has its own dependencies and must be run from the `benchmark/` directory: + +```sh +cd benchmark +npm install +npm run all +``` + +This computes output sizes, runs the timing benchmarks, and generates `benchmark/results/benchmark.html`. Open that report to inspect the results. To run individual stages, use `npm run sizes`, `npm run bench`, or `npm run report` from `benchmark/`. + +## Open a pull request + +1. Review your changes and make sure no unrelated generated files or benchmark results are included. +2. Run the relevant tests; include coverage or benchmark results when they help explain the change. +3. Push your branch to your fork: + + ```sh + git push -u origin descriptive-branch-name + ``` + +4. Open a pull request on GitHub against `tbela99/css-parser`'s `master` branch. +5. In the pull request description, explain the problem and the proposed change, link any related issue, and list the checks you ran. Include before-and-after benchmark results for performance-related changes. + +Keep each pull request focused, and be ready to discuss or revise the change during review. diff --git a/benchmark/package.json b/benchmark/package.json index 4e75aa7c0..041b5fa8d 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -10,8 +10,8 @@ "all": "npm run sizes && npm run bench; npm run report" }, "dependencies": { - "@tbela99/css-parser": "^1.6.5", - "@tbela99/css-parser2": "github:tbela99/css-parser#b74aa264", + "@tbela99/css-parser": "^1.6.6", + "@tbela99/css-parser2": "github:tbela99/css-parser#2a765db", "clean-css": "^5.3.3", "css-tree": "^3.2.1", "cssnano": "^9.0.5", diff --git a/dist/index-umd-web.js b/dist/index-umd-web.js index e8505ba55..70af9db35 100644 --- a/dist/index-umd-web.js +++ b/dist/index-umd-web.js @@ -10027,6 +10027,7 @@ return g > 1 ? [a / g, b / g] : [a, b]; } + const numbers = new Set(["sin", "cos", "asin", "acos", "sign", "tan"]); /** * evaluate an array of tokens * @param tokens @@ -10216,9 +10217,22 @@ r.typ == exports.EnumToken.NegativeInfinityTokenType) { return computeInfinity(l, r, op, defaultReturn); } + if (l.typ == exports.EnumToken.FunctionTokenType || l.typ == exports.EnumToken.MathFunctionTokenType) { + const val = evaluateFunc(l); + if (val == null || + (val.length == 1 && + (val[0].typ == exports.EnumToken.MathFunctionTokenType || val[0].typ == exports.EnumToken.FunctionTokenType))) { + return defaultReturn; + } + if (val.length == 1) { + l = val[0]; + } + } if (r.typ == exports.EnumToken.FunctionTokenType || r.typ == exports.EnumToken.MathFunctionTokenType) { const val = evaluateFunc(r); - if (val == null) { + if (val == null || + (val.length == 1 && + (val[0].typ == exports.EnumToken.MathFunctionTokenType || val[0].typ == exports.EnumToken.FunctionTokenType))) { return defaultReturn; } if (val.length == 1) { @@ -10436,45 +10450,43 @@ function evaluateFunc(token) { const values = token.chi.slice(); switch (token.val) { - case "abs": case "sin": case "cos": case "tan": + for (let i = 0; i < values.length; i++) { + if (values[i].typ == exports.EnumToken.NumberTokenType) { + values[i] = Object.assign(values[i], { typ: exports.EnumToken.AngleTokenType, unit: "rad" }); + } + else if (values[i].typ == exports.EnumToken.AngleTokenType && values[i].unit != "rad") { + switch (values[i].unit) { + case "deg": + Object.assign(values[i], { + unit: "rad", + val: values[i].val * (Math.PI / 180), + }); + break; + case "grad": + Object.assign(values[i], { + unit: "rad", + val: values[i].val * (Math.PI / 200), + }); + break; + case "turn": + Object.assign(values[i], { + unit: "rad", + val: values[i].val * (2 * Math.PI), + }); + break; + } + } + } case "asin": case "acos": case "atan": + case "abs": case "sign": case "sqrt": - case "exp": { - if (token.val == "tan" || token.val == "atan") { - for (let i = 0; i < values.length; i++) { - if (values[i].typ == exports.EnumToken.NumberTokenType) { - values[i] = Object.assign(values[i], { typ: exports.EnumToken.AngleTokenType, unit: "rad" }); - } - else if (values[i].typ == exports.EnumToken.AngleTokenType && values[i].unit != "rad") { - switch (values[i].unit) { - case "deg": - Object.assign(values[i], { - unit: "rad", - val: values[i].val * (Math.PI / 180), - }); - break; - case "grad": - Object.assign(values[i], { - unit: "rad", - val: values[i].val * (Math.PI / 200), - }); - break; - case "turn": - Object.assign(values[i], { - unit: "rad", - val: values[i].val * (2 * Math.PI), - }); - break; - } - } - } - } + case "exp": const value = evaluate(values); // @ts-ignore let val = value[0].typ == exports.EnumToken.NumberTokenType || @@ -10484,7 +10496,7 @@ : // @ts-expect-error value[0].l.val / value[0].r.val; return [ - token.val == "tan" || token.val == "atan" + token.val == "asin" || token.val == "acos" || token.val == "atan" ? { typ: exports.EnumToken.AngleTokenType, val: Math[token.val](val), @@ -10495,7 +10507,7 @@ [PARENT]: value[0][PARENT], } : { - typ: token.val == "sign" ? exports.EnumToken.NumberTokenType : value[0].typ, + typ: numbers.has(token.val) ? exports.EnumToken.NumberTokenType : value[0].typ, val: Math[token.val](val), [LOCSRCID]: value[0][LOCSRCID], [LOCSTA]: value[0][LOCSTA], @@ -10503,7 +10515,6 @@ [PARENT]: value[0][PARENT], }, ]; - } case "hypot": { const chi = values.filter((t) => ![exports.EnumToken.WhitespaceTokenType, exports.EnumToken.CommentTokenType, exports.EnumToken.CommaTokenType].includes(t.typ)); let all = []; @@ -12258,7 +12269,8 @@ return { valid: true, success: false, isValueAllowed: false }; } featureName = featureName.toLowerCase(); - if (!(featureName in config$3.mediaFeatures)) { + // @ts-expect-error + if (config$3.mediaFeatures[featureName] == null) { return { valid: false, success: false }; } // @ts-expect-error @@ -12302,8 +12314,7 @@ return { valid: true, success: (tokens.length == 1 && - tokens[0].typ == exports.EnumToken.NumberTokenType && - tokens[0].val.typ == exports.EnumToken.FractionTokenType) || + tokens[0].typ == exports.EnumToken.NumberTokenType) || (tokens.length === 3 && tokens[0].typ == exports.EnumToken.NumberTokenType && typeof tokens[0].val === "number" && @@ -21047,7 +21058,8 @@ } } run(ast) { - if (!("chi" in ast)) { + // @ts-ignore + if (ast.chi == null) { return null; } for (const node of ast.chi) { @@ -21067,12 +21079,12 @@ } catch (e) { // @ts-ignore - if (Array.isArray(parent.chi)) { - // @ts-ignore - parent.chi.length = 0; - // @ts-ignore - parent.chi.push(...result); - } + // if (Array.isArray(parent.chi)) { + // // @ts-ignore + // parent.chi.length = 0; + // // @ts-ignore + // parent.chi.push(...result); + // } } continue; } @@ -30265,7 +30277,7 @@ }; } { - for (const stream of parts.slice()) { + for (const stream of parts) { let success = true; let i; let currentScope = new Set(); @@ -30384,6 +30396,22 @@ [LOCSTA]: names[0][LOCSTA], [LOCEND]: values.at(-1)[LOCEND], }); + const token = tokens[index3 + 1]; + if (token.op.typ === exports.EnumToken.ColonTokenType) { + let name = token.l.find((t) => t.typ === exports.EnumToken.IdenTokenType); + if (name != null) { + if (name.val.startsWith("min-")) { + name.val = name.val.substring(4); + // @ts-ignore + token.op.typ = exports.EnumToken.GteTokenType; + } + else if (name.val.startsWith("max-")) { + name.val = name.val.substring(4); + // @ts-ignore + token.op.typ = exports.EnumToken.LteTokenType; + } + } + } // check or } if (tokensfuncDefMap.has(stack.at(-1)?.typ)) { @@ -30475,6 +30503,58 @@ }; tokens.length = l + 1; expectAndOr = true; + if (tokens[l].op.typ === exports.EnumToken.AndTokenType) { + let left = tokens[l].l.find((t) => t.typ != exports.EnumToken.WhitespaceTokenType && t.typ != exports.EnumToken.IdenTokenType); + let right = tokens[l].r.find((t) => t.typ != exports.EnumToken.WhitespaceTokenType && t.typ != exports.EnumToken.IdenTokenType); + if (left.typ == exports.EnumToken.ParensTokenType && right.typ == exports.EnumToken.ParensTokenType) { + left = left.chi.find((t) => t.typ != exports.EnumToken.WhitespaceTokenType && t.typ != exports.EnumToken.IdenTokenType); + right = right.chi.find((t) => t.typ != exports.EnumToken.WhitespaceTokenType && t.typ != exports.EnumToken.IdenTokenType); + if (left.typ == exports.EnumToken.MediaQueryConditionTokenType && + (mFLT.has(left.op.typ) || + mFGT.has(left.op.typ)) && + right.typ == exports.EnumToken.MediaQueryConditionTokenType && + (mFLT.has(right.op.typ) || + mFGT.has(right.op.typ))) { + const name = left.l.find((t) => t.typ == exports.EnumToken.IdenTokenType); + const name2 = right.l.find((t) => t.typ == exports.EnumToken.IdenTokenType); + if (equalsIgnoreCase(name.val, name2.val)) { + switch (left.op.typ) { + case exports.EnumToken.LtTokenType: + left.op.typ = exports.EnumToken.GtTokenType; + break; + case exports.EnumToken.LteTokenType: + left.op.typ = exports.EnumToken.GteTokenType; + break; + case exports.EnumToken.GtTokenType: + left.op.typ = exports.EnumToken.LtTokenType; + break; + case exports.EnumToken.GteTokenType: + left.op.typ = exports.EnumToken.LteTokenType; + break; + } + tokens[l] = { + typ: exports.EnumToken.ParensTokenType, + chi: [ + { + typ: exports.EnumToken.MediaRangeQueryTokenType, + l: left.r, + val: [name], + op1: left.op, + op2: right.op, + r: right.r, + [LOCSRCID]: name[LOCSRCID], + [LOCSTA]: left[LOCSTA], + [LOCEND]: right[LOCEND], + }, + ], + [LOCSRCID]: name[LOCSRCID], + [LOCSTA]: left[LOCSTA], + [LOCEND]: right[LOCEND], + }; + } + } + } + } } break; } @@ -31108,7 +31188,6 @@ // let currentItemIndex: number; ast[LOCSRCID] = options.source.id; ast[LOCSTA] = 0; - // let tokenizer: Tokenizer; while (!tokenizer.done()) { tokenizer.next(); // item = (iter as Array)[currentItemIndex]; diff --git a/dist/index.cjs b/dist/index.cjs index 1c95d5ec5..7ec5b7433 100644 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -10030,6 +10030,7 @@ function simplify(a, b) { return g > 1 ? [a / g, b / g] : [a, b]; } +const numbers = new Set(["sin", "cos", "asin", "acos", "sign", "tan"]); /** * evaluate an array of tokens * @param tokens @@ -10219,9 +10220,22 @@ function doEvaluate(l, r, op) { r.typ == exports.EnumToken.NegativeInfinityTokenType) { return computeInfinity(l, r, op, defaultReturn); } + if (l.typ == exports.EnumToken.FunctionTokenType || l.typ == exports.EnumToken.MathFunctionTokenType) { + const val = evaluateFunc(l); + if (val == null || + (val.length == 1 && + (val[0].typ == exports.EnumToken.MathFunctionTokenType || val[0].typ == exports.EnumToken.FunctionTokenType))) { + return defaultReturn; + } + if (val.length == 1) { + l = val[0]; + } + } if (r.typ == exports.EnumToken.FunctionTokenType || r.typ == exports.EnumToken.MathFunctionTokenType) { const val = evaluateFunc(r); - if (val == null) { + if (val == null || + (val.length == 1 && + (val[0].typ == exports.EnumToken.MathFunctionTokenType || val[0].typ == exports.EnumToken.FunctionTokenType))) { return defaultReturn; } if (val.length == 1) { @@ -10439,45 +10453,43 @@ function getValue$1(t) { function evaluateFunc(token) { const values = token.chi.slice(); switch (token.val) { - case "abs": case "sin": case "cos": case "tan": + for (let i = 0; i < values.length; i++) { + if (values[i].typ == exports.EnumToken.NumberTokenType) { + values[i] = Object.assign(values[i], { typ: exports.EnumToken.AngleTokenType, unit: "rad" }); + } + else if (values[i].typ == exports.EnumToken.AngleTokenType && values[i].unit != "rad") { + switch (values[i].unit) { + case "deg": + Object.assign(values[i], { + unit: "rad", + val: values[i].val * (Math.PI / 180), + }); + break; + case "grad": + Object.assign(values[i], { + unit: "rad", + val: values[i].val * (Math.PI / 200), + }); + break; + case "turn": + Object.assign(values[i], { + unit: "rad", + val: values[i].val * (2 * Math.PI), + }); + break; + } + } + } case "asin": case "acos": case "atan": + case "abs": case "sign": case "sqrt": - case "exp": { - if (token.val == "tan" || token.val == "atan") { - for (let i = 0; i < values.length; i++) { - if (values[i].typ == exports.EnumToken.NumberTokenType) { - values[i] = Object.assign(values[i], { typ: exports.EnumToken.AngleTokenType, unit: "rad" }); - } - else if (values[i].typ == exports.EnumToken.AngleTokenType && values[i].unit != "rad") { - switch (values[i].unit) { - case "deg": - Object.assign(values[i], { - unit: "rad", - val: values[i].val * (Math.PI / 180), - }); - break; - case "grad": - Object.assign(values[i], { - unit: "rad", - val: values[i].val * (Math.PI / 200), - }); - break; - case "turn": - Object.assign(values[i], { - unit: "rad", - val: values[i].val * (2 * Math.PI), - }); - break; - } - } - } - } + case "exp": const value = evaluate(values); // @ts-ignore let val = value[0].typ == exports.EnumToken.NumberTokenType || @@ -10487,7 +10499,7 @@ function evaluateFunc(token) { : // @ts-expect-error value[0].l.val / value[0].r.val; return [ - token.val == "tan" || token.val == "atan" + token.val == "asin" || token.val == "acos" || token.val == "atan" ? { typ: exports.EnumToken.AngleTokenType, val: Math[token.val](val), @@ -10498,7 +10510,7 @@ function evaluateFunc(token) { [PARENT]: value[0][PARENT], } : { - typ: token.val == "sign" ? exports.EnumToken.NumberTokenType : value[0].typ, + typ: numbers.has(token.val) ? exports.EnumToken.NumberTokenType : value[0].typ, val: Math[token.val](val), [LOCSRCID]: value[0][LOCSRCID], [LOCSTA]: value[0][LOCSTA], @@ -10506,7 +10518,6 @@ function evaluateFunc(token) { [PARENT]: value[0][PARENT], }, ]; - } case "hypot": { const chi = values.filter((t) => ![exports.EnumToken.WhitespaceTokenType, exports.EnumToken.CommentTokenType, exports.EnumToken.CommaTokenType].includes(t.typ)); let all = []; @@ -12261,7 +12272,8 @@ function isMFValue(featureName, tokens, isMFRange) { return { valid: true, success: false, isValueAllowed: false }; } featureName = featureName.toLowerCase(); - if (!(featureName in config$3.mediaFeatures)) { + // @ts-expect-error + if (config$3.mediaFeatures[featureName] == null) { return { valid: false, success: false }; } // @ts-expect-error @@ -12305,8 +12317,7 @@ function isMFValue(featureName, tokens, isMFRange) { return { valid: true, success: (tokens.length == 1 && - tokens[0].typ == exports.EnumToken.NumberTokenType && - tokens[0].val.typ == exports.EnumToken.FractionTokenType) || + tokens[0].typ == exports.EnumToken.NumberTokenType) || (tokens.length === 3 && tokens[0].typ == exports.EnumToken.NumberTokenType && typeof tokens[0].val === "number" && @@ -21050,7 +21061,8 @@ class ComputeCalcExpressionFeature { } } run(ast) { - if (!("chi" in ast)) { + // @ts-ignore + if (ast.chi == null) { return null; } for (const node of ast.chi) { @@ -21070,12 +21082,12 @@ class ComputeCalcExpressionFeature { } catch (e) { // @ts-ignore - if (Array.isArray(parent.chi)) { - // @ts-ignore - parent.chi.length = 0; - // @ts-ignore - parent.chi.push(...result); - } + // if (Array.isArray(parent.chi)) { + // // @ts-ignore + // parent.chi.length = 0; + // // @ts-ignore + // parent.chi.push(...result); + // } } continue; } @@ -30268,7 +30280,7 @@ function parseAtRuleContainerQueryList(stream, context, options = {}) { }; } { - for (const stream of parts.slice()) { + for (const stream of parts) { let success = true; let i; let currentScope = new Set(); @@ -30387,6 +30399,22 @@ function parseAtRuleContainerQueryList(stream, context, options = {}) { [LOCSTA]: names[0][LOCSTA], [LOCEND]: values.at(-1)[LOCEND], }); + const token = tokens[index3 + 1]; + if (token.op.typ === exports.EnumToken.ColonTokenType) { + let name = token.l.find((t) => t.typ === exports.EnumToken.IdenTokenType); + if (name != null) { + if (name.val.startsWith("min-")) { + name.val = name.val.substring(4); + // @ts-ignore + token.op.typ = exports.EnumToken.GteTokenType; + } + else if (name.val.startsWith("max-")) { + name.val = name.val.substring(4); + // @ts-ignore + token.op.typ = exports.EnumToken.LteTokenType; + } + } + } // check or } if (tokensfuncDefMap.has(stack.at(-1)?.typ)) { @@ -30478,6 +30506,58 @@ function parseAtRuleContainerQueryList(stream, context, options = {}) { }; tokens.length = l + 1; expectAndOr = true; + if (tokens[l].op.typ === exports.EnumToken.AndTokenType) { + let left = tokens[l].l.find((t) => t.typ != exports.EnumToken.WhitespaceTokenType && t.typ != exports.EnumToken.IdenTokenType); + let right = tokens[l].r.find((t) => t.typ != exports.EnumToken.WhitespaceTokenType && t.typ != exports.EnumToken.IdenTokenType); + if (left.typ == exports.EnumToken.ParensTokenType && right.typ == exports.EnumToken.ParensTokenType) { + left = left.chi.find((t) => t.typ != exports.EnumToken.WhitespaceTokenType && t.typ != exports.EnumToken.IdenTokenType); + right = right.chi.find((t) => t.typ != exports.EnumToken.WhitespaceTokenType && t.typ != exports.EnumToken.IdenTokenType); + if (left.typ == exports.EnumToken.MediaQueryConditionTokenType && + (mFLT.has(left.op.typ) || + mFGT.has(left.op.typ)) && + right.typ == exports.EnumToken.MediaQueryConditionTokenType && + (mFLT.has(right.op.typ) || + mFGT.has(right.op.typ))) { + const name = left.l.find((t) => t.typ == exports.EnumToken.IdenTokenType); + const name2 = right.l.find((t) => t.typ == exports.EnumToken.IdenTokenType); + if (equalsIgnoreCase(name.val, name2.val)) { + switch (left.op.typ) { + case exports.EnumToken.LtTokenType: + left.op.typ = exports.EnumToken.GtTokenType; + break; + case exports.EnumToken.LteTokenType: + left.op.typ = exports.EnumToken.GteTokenType; + break; + case exports.EnumToken.GtTokenType: + left.op.typ = exports.EnumToken.LtTokenType; + break; + case exports.EnumToken.GteTokenType: + left.op.typ = exports.EnumToken.LteTokenType; + break; + } + tokens[l] = { + typ: exports.EnumToken.ParensTokenType, + chi: [ + { + typ: exports.EnumToken.MediaRangeQueryTokenType, + l: left.r, + val: [name], + op1: left.op, + op2: right.op, + r: right.r, + [LOCSRCID]: name[LOCSRCID], + [LOCSTA]: left[LOCSTA], + [LOCEND]: right[LOCEND], + }, + ], + [LOCSRCID]: name[LOCSRCID], + [LOCSTA]: left[LOCSTA], + [LOCEND]: right[LOCEND], + }; + } + } + } + } } break; } @@ -31111,7 +31191,6 @@ function doParseSync(tokenizer, options = {}) { // let currentItemIndex: number; ast[LOCSRCID] = options.source.id; ast[LOCSTA] = 0; - // let tokenizer: Tokenizer; while (!tokenizer.done()) { tokenizer.next(); // item = (iter as Array)[currentItemIndex]; diff --git a/dist/lib/ast/features/calc.js b/dist/lib/ast/features/calc.js index 67a32fefd..2c4e273b1 100644 --- a/dist/lib/ast/features/calc.js +++ b/dist/lib/ast/features/calc.js @@ -20,7 +20,8 @@ class ComputeCalcExpressionFeature { } } run(ast) { - if (!("chi" in ast)) { + // @ts-ignore + if (ast.chi == null) { return null; } for (const node of ast.chi) { @@ -40,12 +41,12 @@ class ComputeCalcExpressionFeature { } catch (e) { // @ts-ignore - if (Array.isArray(parent.chi)) { - // @ts-ignore - parent.chi.length = 0; - // @ts-ignore - parent.chi.push(...result); - } + // if (Array.isArray(parent.chi)) { + // // @ts-ignore + // parent.chi.length = 0; + // // @ts-ignore + // parent.chi.push(...result); + // } } continue; } diff --git a/dist/lib/ast/math/expression.js b/dist/lib/ast/math/expression.js index 5a04f657e..75e2d5893 100644 --- a/dist/lib/ast/math/expression.js +++ b/dist/lib/ast/math/expression.js @@ -3,6 +3,7 @@ import { mathFuncs, PARENT, LOCEND, LOCSTA, LOCSRCID } from '../../syntax/consta import { EnumToken } from '../types.js'; import { rem, compute } from './math.js'; +const numbers = new Set(["sin", "cos", "asin", "acos", "sign", "tan"]); /** * evaluate an array of tokens * @param tokens @@ -192,9 +193,22 @@ function doEvaluate(l, r, op) { r.typ == EnumToken.NegativeInfinityTokenType) { return computeInfinity(l, r, op, defaultReturn); } + if (l.typ == EnumToken.FunctionTokenType || l.typ == EnumToken.MathFunctionTokenType) { + const val = evaluateFunc(l); + if (val == null || + (val.length == 1 && + (val[0].typ == EnumToken.MathFunctionTokenType || val[0].typ == EnumToken.FunctionTokenType))) { + return defaultReturn; + } + if (val.length == 1) { + l = val[0]; + } + } if (r.typ == EnumToken.FunctionTokenType || r.typ == EnumToken.MathFunctionTokenType) { const val = evaluateFunc(r); - if (val == null) { + if (val == null || + (val.length == 1 && + (val[0].typ == EnumToken.MathFunctionTokenType || val[0].typ == EnumToken.FunctionTokenType))) { return defaultReturn; } if (val.length == 1) { @@ -412,45 +426,43 @@ function getValue(t) { function evaluateFunc(token) { const values = token.chi.slice(); switch (token.val) { - case "abs": case "sin": case "cos": case "tan": + for (let i = 0; i < values.length; i++) { + if (values[i].typ == EnumToken.NumberTokenType) { + values[i] = Object.assign(values[i], { typ: EnumToken.AngleTokenType, unit: "rad" }); + } + else if (values[i].typ == EnumToken.AngleTokenType && values[i].unit != "rad") { + switch (values[i].unit) { + case "deg": + Object.assign(values[i], { + unit: "rad", + val: values[i].val * (Math.PI / 180), + }); + break; + case "grad": + Object.assign(values[i], { + unit: "rad", + val: values[i].val * (Math.PI / 200), + }); + break; + case "turn": + Object.assign(values[i], { + unit: "rad", + val: values[i].val * (2 * Math.PI), + }); + break; + } + } + } case "asin": case "acos": case "atan": + case "abs": case "sign": case "sqrt": - case "exp": { - if (token.val == "tan" || token.val == "atan") { - for (let i = 0; i < values.length; i++) { - if (values[i].typ == EnumToken.NumberTokenType) { - values[i] = Object.assign(values[i], { typ: EnumToken.AngleTokenType, unit: "rad" }); - } - else if (values[i].typ == EnumToken.AngleTokenType && values[i].unit != "rad") { - switch (values[i].unit) { - case "deg": - Object.assign(values[i], { - unit: "rad", - val: values[i].val * (Math.PI / 180), - }); - break; - case "grad": - Object.assign(values[i], { - unit: "rad", - val: values[i].val * (Math.PI / 200), - }); - break; - case "turn": - Object.assign(values[i], { - unit: "rad", - val: values[i].val * (2 * Math.PI), - }); - break; - } - } - } - } + case "exp": const value = evaluate(values); // @ts-ignore let val = value[0].typ == EnumToken.NumberTokenType || @@ -460,7 +472,7 @@ function evaluateFunc(token) { : // @ts-expect-error value[0].l.val / value[0].r.val; return [ - token.val == "tan" || token.val == "atan" + token.val == "asin" || token.val == "acos" || token.val == "atan" ? { typ: EnumToken.AngleTokenType, val: Math[token.val](val), @@ -471,7 +483,7 @@ function evaluateFunc(token) { [PARENT]: value[0][PARENT], } : { - typ: token.val == "sign" ? EnumToken.NumberTokenType : value[0].typ, + typ: numbers.has(token.val) ? EnumToken.NumberTokenType : value[0].typ, val: Math[token.val](val), [LOCSRCID]: value[0][LOCSRCID], [LOCSTA]: value[0][LOCSTA], @@ -479,7 +491,6 @@ function evaluateFunc(token) { [PARENT]: value[0][PARENT], }, ]; - } case "hypot": { const chi = values.filter((t) => ![EnumToken.WhitespaceTokenType, EnumToken.CommentTokenType, EnumToken.CommaTokenType].includes(t.typ)); let all = []; diff --git a/dist/lib/parser/parse.js b/dist/lib/parser/parse.js index d4ed48bb7..803c6868f 100644 --- a/dist/lib/parser/parse.js +++ b/dist/lib/parser/parse.js @@ -506,7 +506,6 @@ function doParseSync(tokenizer, options = {}) { // let currentItemIndex: number; ast[LOCSRCID] = options.source.id; ast[LOCSTA] = 0; - // let tokenizer: Tokenizer; while (!tokenizer.done()) { tokenizer.next(); // item = (iter as Array)[currentItemIndex]; diff --git a/dist/lib/parser/utils/at-rule-container.js b/dist/lib/parser/utils/at-rule-container.js index c70b54cbc..18d5863ca 100644 --- a/dist/lib/parser/utils/at-rule-container.js +++ b/dist/lib/parser/utils/at-rule-container.js @@ -3,6 +3,7 @@ import { tokensfuncDefMap, LOCSTA, mFGT, mFLT, LOCEND, LOCSRCID } from '../../sy import { matchAllSyntaxes, createValidationContext, trimArray } from '../../validation/match.js'; import { ValidationSyntaxGroupEnum } from '../../validation/parser/typedef.js'; import { getSyntaxRule } from '../../validation/config.js'; +import { equalsIgnoreCase } from './text.js'; function parseAtRuleContainerQueryList(stream, context, options = {}) { let matchCount = 0; @@ -37,7 +38,7 @@ function parseAtRuleContainerQueryList(stream, context, options = {}) { }; } { - for (const stream of parts.slice()) { + for (const stream of parts) { let success = true; let i; let currentScope = new Set(); @@ -156,6 +157,22 @@ function parseAtRuleContainerQueryList(stream, context, options = {}) { [LOCSTA]: names[0][LOCSTA], [LOCEND]: values.at(-1)[LOCEND], }); + const token = tokens[index3 + 1]; + if (token.op.typ === EnumToken.ColonTokenType) { + let name = token.l.find((t) => t.typ === EnumToken.IdenTokenType); + if (name != null) { + if (name.val.startsWith("min-")) { + name.val = name.val.substring(4); + // @ts-ignore + token.op.typ = EnumToken.GteTokenType; + } + else if (name.val.startsWith("max-")) { + name.val = name.val.substring(4); + // @ts-ignore + token.op.typ = EnumToken.LteTokenType; + } + } + } // check or } if (tokensfuncDefMap.has(stack.at(-1)?.typ)) { @@ -247,6 +264,58 @@ function parseAtRuleContainerQueryList(stream, context, options = {}) { }; tokens.length = l + 1; expectAndOr = true; + if (tokens[l].op.typ === EnumToken.AndTokenType) { + let left = tokens[l].l.find((t) => t.typ != EnumToken.WhitespaceTokenType && t.typ != EnumToken.IdenTokenType); + let right = tokens[l].r.find((t) => t.typ != EnumToken.WhitespaceTokenType && t.typ != EnumToken.IdenTokenType); + if (left.typ == EnumToken.ParensTokenType && right.typ == EnumToken.ParensTokenType) { + left = left.chi.find((t) => t.typ != EnumToken.WhitespaceTokenType && t.typ != EnumToken.IdenTokenType); + right = right.chi.find((t) => t.typ != EnumToken.WhitespaceTokenType && t.typ != EnumToken.IdenTokenType); + if (left.typ == EnumToken.MediaQueryConditionTokenType && + (mFLT.has(left.op.typ) || + mFGT.has(left.op.typ)) && + right.typ == EnumToken.MediaQueryConditionTokenType && + (mFLT.has(right.op.typ) || + mFGT.has(right.op.typ))) { + const name = left.l.find((t) => t.typ == EnumToken.IdenTokenType); + const name2 = right.l.find((t) => t.typ == EnumToken.IdenTokenType); + if (equalsIgnoreCase(name.val, name2.val)) { + switch (left.op.typ) { + case EnumToken.LtTokenType: + left.op.typ = EnumToken.GtTokenType; + break; + case EnumToken.LteTokenType: + left.op.typ = EnumToken.GteTokenType; + break; + case EnumToken.GtTokenType: + left.op.typ = EnumToken.LtTokenType; + break; + case EnumToken.GteTokenType: + left.op.typ = EnumToken.LteTokenType; + break; + } + tokens[l] = { + typ: EnumToken.ParensTokenType, + chi: [ + { + typ: EnumToken.MediaRangeQueryTokenType, + l: left.r, + val: [name], + op1: left.op, + op2: right.op, + r: right.r, + [LOCSRCID]: name[LOCSRCID], + [LOCSTA]: left[LOCSTA], + [LOCEND]: right[LOCEND], + }, + ], + [LOCSRCID]: name[LOCSRCID], + [LOCSTA]: left[LOCSTA], + [LOCEND]: right[LOCEND], + }; + } + } + } + } } break; } diff --git a/dist/lib/validation/match.js b/dist/lib/validation/match.js index 3ee9a31e4..fe0076b02 100644 --- a/dist/lib/validation/match.js +++ b/dist/lib/validation/match.js @@ -82,7 +82,8 @@ function isMFValue(featureName, tokens, isMFRange) { return { valid: true, success: false, isValueAllowed: false }; } featureName = featureName.toLowerCase(); - if (!(featureName in config.mediaFeatures)) { + // @ts-expect-error + if (config.mediaFeatures[featureName] == null) { return { valid: false, success: false }; } // @ts-expect-error @@ -126,8 +127,7 @@ function isMFValue(featureName, tokens, isMFRange) { return { valid: true, success: (tokens.length == 1 && - tokens[0].typ == EnumToken.NumberTokenType && - tokens[0].val.typ == EnumToken.FractionTokenType) || + tokens[0].typ == EnumToken.NumberTokenType) || (tokens.length === 3 && tokens[0].typ == EnumToken.NumberTokenType && typeof tokens[0].val === "number" && diff --git a/jsr.json b/jsr.json index 79c8ddfe0..280febcf1 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@tbela99/css-parser", - "version": "1.6.5", + "version": "1.6.6", "publish": { "include": [ "src", diff --git a/package.json b/package.json index cdbd5ec37..cf4064835 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@tbela99/css-parser", "description": "CSS parser, minifier and validator for node and the browser", - "version": "1.6.5", + "version": "1.6.6", "exports": { ".": "./dist/node.js", "./node": "./dist/node.js", @@ -68,17 +68,17 @@ "@types/chai": "^5.2.3", "@types/chai-as-promised": "^8.0.2", "@types/mocha": "^10.0.10", - "@types/node": "^26.4.1", - "@types/web": "^0.0.354", + "@types/node": "^26.6.4", + "@types/web": "^0.0.357", "@web/test-runner": "^1.0.0", "@web/test-runner-playwright": "^1.0.0", - "chai": "^6.2.2", + "chai": "^6.3.0", "chai-as-promised": "^8.0.2", "esno": "^4.8.0", "markdown-it-task-lists": "^2.1.1", - "mocha": "^12.0.0", - "playwright": "^1.62.1", - "rollup": "^4.63.1", + "mocha": "^12.0.3", + "playwright": "^1.63.0", + "rollup": "^4.63.6", "rollup-plugin-dts": "^6.5.1", "superc8": "^12.7.0", "tslib": "^2.8.1", @@ -89,4 +89,4 @@ "typedoc-unhoax-theme": "^0.5.3" }, "sideEffects": false -} \ No newline at end of file +} diff --git a/src/data/validation.json b/src/data/validation.json index ca7ebc0cb..0fe14b61e 100644 --- a/src/data/validation.json +++ b/src/data/validation.json @@ -1,5359 +1 @@ -{ - "declarations": { - "-ms-accelerator": { - "syntax": "false | true" - }, - "-ms-block-progression": { - "syntax": "tb | rl | bt | lr" - }, - "-ms-content-zoom-chaining": { - "syntax": "none | chained" - }, - "-ms-content-zoom-limit": { - "syntax": "<'-ms-content-zoom-limit-min'> <'-ms-content-zoom-limit-max'>" - }, - "-ms-content-zoom-limit-max": { - "syntax": "" - }, - "-ms-content-zoom-limit-min": { - "syntax": "" - }, - "-ms-content-zoom-snap": { - "syntax": "<'-ms-content-zoom-snap-type'> || <'-ms-content-zoom-snap-points'>" - }, - "-ms-content-zoom-snap-points": { - "syntax": "snapInterval( , ) | snapList( # )" - }, - "-ms-content-zoom-snap-type": { - "syntax": "none | proximity | mandatory" - }, - "-ms-content-zooming": { - "syntax": "none | zoom" - }, - "-ms-filter": { - "syntax": "" - }, - "-ms-flow-from": { - "syntax": "[ none | ]#" - }, - "-ms-flow-into": { - "syntax": "[ none | ]#" - }, - "-ms-grid-columns": { - "syntax": "none | | " - }, - "-ms-grid-rows": { - "syntax": "none | | " - }, - "-ms-high-contrast-adjust": { - "syntax": "auto | none" - }, - "-ms-hyphenate-limit-chars": { - "syntax": "auto | {1,3}" - }, - "-ms-hyphenate-limit-lines": { - "syntax": "no-limit | " - }, - "-ms-hyphenate-limit-zone": { - "syntax": " | " - }, - "-ms-ime-align": { - "syntax": "auto | after" - }, - "-ms-overflow-style": { - "syntax": " auto | none | scrollbar | -ms-autohiding-scrollbar" - }, - "-ms-scroll-chaining": { - "syntax": "chained | none" - }, - "-ms-scroll-limit": { - "syntax": "<'-ms-scroll-limit-x-min'> <'-ms-scroll-limit-y-min'> <'-ms-scroll-limit-x-max'> <'-ms-scroll-limit-y-max'>" - }, - "-ms-scroll-limit-x-max": { - "syntax": "auto | " - }, - "-ms-scroll-limit-x-min": { - "syntax": "" - }, - "-ms-scroll-limit-y-max": { - "syntax": "auto | " - }, - "-ms-scroll-limit-y-min": { - "syntax": "" - }, - "-ms-scroll-rails": { - "syntax": "none | railed" - }, - "-ms-scroll-snap-points-x": { - "syntax": "snapInterval( , ) | snapList( # )" - }, - "-ms-scroll-snap-points-y": { - "syntax": "snapInterval( , ) | snapList( # )" - }, - "-ms-scroll-snap-type": { - "syntax": "none | proximity | mandatory" - }, - "-ms-scroll-snap-x": { - "syntax": "<'-ms-scroll-snap-type'> <'-ms-scroll-snap-points-x'>" - }, - "-ms-scroll-snap-y": { - "syntax": "<'-ms-scroll-snap-type'> <'-ms-scroll-snap-points-y'>" - }, - "-ms-scroll-translation": { - "syntax": "none | vertical-to-horizontal" - }, - "-ms-scrollbar-3dlight-color": { - "syntax": "" - }, - "-ms-scrollbar-arrow-color": { - "syntax": "" - }, - "-ms-scrollbar-base-color": { - "syntax": "" - }, - "-ms-scrollbar-darkshadow-color": { - "syntax": "" - }, - "-ms-scrollbar-face-color": { - "syntax": "" - }, - "-ms-scrollbar-highlight-color": { - "syntax": "" - }, - "-ms-scrollbar-shadow-color": { - "syntax": "" - }, - "-ms-scrollbar-track-color": { - "syntax": "" - }, - "-ms-text-autospace": { - "syntax": "none | ideograph-alpha | ideograph-numeric | ideograph-parenthesis | ideograph-space" - }, - "-ms-touch-select": { - "syntax": "grippers | none" - }, - "-ms-user-select": { - "syntax": "none | element | text" - }, - "-ms-wrap-flow": { - "syntax": "auto | both | start | end | maximum | clear" - }, - "-ms-wrap-margin": { - "syntax": "" - }, - "-ms-wrap-through": { - "syntax": "wrap | none" - }, - "-moz-appearance": { - "syntax": "none | button | button-arrow-down | button-arrow-next | button-arrow-previous | button-arrow-up | button-bevel | button-focus | caret | checkbox | checkbox-container | checkbox-label | checkmenuitem | dualbutton | groupbox | listbox | listitem | menuarrow | menubar | menucheckbox | menuimage | menuitem | menuitemtext | menulist | menulist-button | menulist-text | menulist-textfield | menupopup | menuradio | menuseparator | meterbar | meterchunk | progressbar | progressbar-vertical | progresschunk | progresschunk-vertical | radio | radio-container | radio-label | radiomenuitem | range | range-thumb | resizer | resizerpanel | scale-horizontal | scalethumbend | scalethumb-horizontal | scalethumbstart | scalethumbtick | scalethumb-vertical | scale-vertical | scrollbarbutton-down | scrollbarbutton-left | scrollbarbutton-right | scrollbarbutton-up | scrollbarthumb-horizontal | scrollbarthumb-vertical | scrollbartrack-horizontal | scrollbartrack-vertical | searchfield | separator | sheet | spinner | spinner-downbutton | spinner-textfield | spinner-upbutton | splitter | statusbar | statusbarpanel | tab | tabpanel | tabpanels | tab-scroll-arrow-back | tab-scroll-arrow-forward | textfield | textfield-multiline | toolbar | toolbarbutton | toolbarbutton-dropdown | toolbargripper | toolbox | tooltip | treeheader | treeheadercell | treeheadersortarrow | treeitem | treeline | treetwisty | treetwistyopen | treeview | -moz-mac-unified-toolbar | -moz-win-borderless-glass | -moz-win-browsertabbar-toolbox | -moz-win-communicationstext | -moz-win-communications-toolbox | -moz-win-exclude-glass | -moz-win-glass | -moz-win-mediatext | -moz-win-media-toolbox | -moz-window-button-box | -moz-window-button-box-maximized | -moz-window-button-close | -moz-window-button-maximize | -moz-window-button-minimize | -moz-window-button-restore | -moz-window-frame-bottom | -moz-window-frame-left | -moz-window-frame-right | -moz-window-titlebar | -moz-window-titlebar-maximized" - }, - "-moz-binding": { - "syntax": " | none" - }, - "-moz-border-bottom-colors": { - "syntax": "+ | none" - }, - "-moz-border-left-colors": { - "syntax": "+ | none" - }, - "-moz-border-right-colors": { - "syntax": "+ | none" - }, - "-moz-border-top-colors": { - "syntax": "+ | none" - }, - "-moz-context-properties": { - "syntax": "none | [ fill | fill-opacity | stroke | stroke-opacity ]#" - }, - "-moz-float-edge": { - "syntax": "border-box | content-box | margin-box | padding-box" - }, - "-moz-force-broken-image-icon": { - "syntax": "0 | 1" - }, - "-moz-orient": { - "syntax": "inline | block | horizontal | vertical" - }, - "-moz-outline-radius": { - "syntax": "{1,4} [ / {1,4} ]?" - }, - "-moz-outline-radius-bottomleft": { - "syntax": "" - }, - "-moz-outline-radius-bottomright": { - "syntax": "" - }, - "-moz-outline-radius-topleft": { - "syntax": "" - }, - "-moz-outline-radius-topright": { - "syntax": "" - }, - "-moz-stack-sizing": { - "syntax": "ignore | stretch-to-fit" - }, - "-moz-text-blink": { - "syntax": "none | blink" - }, - "-moz-user-focus": { - "syntax": "ignore | normal | select-after | select-before | select-menu | select-same | select-all | none" - }, - "-moz-user-input": { - "syntax": "auto | none | enabled | disabled" - }, - "-moz-user-modify": { - "syntax": "read-only | read-write | write-only" - }, - "-moz-window-dragging": { - "syntax": "drag | no-drag" - }, - "-moz-window-shadow": { - "syntax": "default | menu | tooltip | sheet | none" - }, - "-webkit-appearance": { - "syntax": " none | button | button-bevel | caps-lock-indicator | caret | checkbox | default-button | inner-spin-button | listbox | listitem | media-controls-background | media-controls-fullscreen-background | media-current-time-display | media-enter-fullscreen-button | media-exit-fullscreen-button | media-fullscreen-button | media-mute-button | media-overlay-play-button | media-play-button | media-seek-back-button | media-seek-forward-button | media-slider | media-sliderthumb | media-time-remaining-display | media-toggle-closed-captions-button | media-volume-slider | media-volume-slider-container | media-volume-sliderthumb | menulist | menulist-button | menulist-text | menulist-textfield | meter | progress-bar | progress-bar-value | push-button | radio | scrollbarbutton-down | scrollbarbutton-left | scrollbarbutton-right | scrollbarbutton-up | scrollbargripper-horizontal | scrollbargripper-vertical | scrollbarthumb-horizontal | scrollbarthumb-vertical | scrollbartrack-horizontal | scrollbartrack-vertical | searchfield | searchfield-cancel-button | searchfield-decoration | searchfield-results-button | searchfield-results-decoration | slider-horizontal | slider-vertical | sliderthumb-horizontal | sliderthumb-vertical | square-button | textarea | textfield | -apple-pay-button" - }, - "-webkit-border-after": { - "syntax": "<'border-top'>" - }, - "-webkit-border-after-color": { - "syntax": "<'border-top-color'>" - }, - "-webkit-border-after-style": { - "syntax": "<'border-top-style'>" - }, - "-webkit-border-after-width": { - "syntax": "<'border-top-width'>" - }, - "-webkit-border-before": { - "syntax": "<'border-top'>" - }, - "-webkit-border-before-color": { - "syntax": "<'border-top-color'>" - }, - "-webkit-border-before-style": { - "syntax": "<'border-top-style'>" - }, - "-webkit-border-before-width": { - "syntax": "<'border-top-width'>" - }, - "-webkit-border-end": { - "syntax": "<'border-top'>" - }, - "-webkit-border-end-color": { - "syntax": "<'border-top-color'>" - }, - "-webkit-border-end-style": { - "syntax": "<'border-top-style'>" - }, - "-webkit-border-end-width": { - "syntax": "<'border-top-width'>" - }, - "-webkit-border-start": { - "syntax": "<'border-top'>" - }, - "-webkit-border-start-color": { - "syntax": "<'border-top-color'>" - }, - "-webkit-border-start-style": { - "syntax": "<'border-top-style'>" - }, - "-webkit-border-start-width": { - "syntax": "<'border-top-width'>" - }, - "-webkit-box-reflect": { - "syntax": "[ above | below | right | left ]? ? ?" - }, - "-webkit-line-clamp": { - "syntax": "none | " - }, - "-webkit-mask": { - "syntax": "[ || [ / ]? || || [ | border | padding | content | text ] || [ | border | padding | content ] ]#" - }, - "-webkit-mask-attachment": { - "syntax": "#" - }, - "-webkit-mask-clip": { - "syntax": "[ | no-clip | border | padding | content | text ]#" - }, - "-webkit-mask-composite": { - "syntax": "#" - }, - "-webkit-mask-image": { - "syntax": "#" - }, - "-webkit-mask-origin": { - "syntax": "[ | border | padding | content ]#" - }, - "-webkit-mask-position": { - "syntax": "#" - }, - "-webkit-mask-position-x": { - "syntax": "[ | left | center | right ]#" - }, - "-webkit-mask-position-y": { - "syntax": "[ | top | center | bottom ]#" - }, - "-webkit-mask-repeat": { - "syntax": "#" - }, - "-webkit-mask-repeat-x": { - "syntax": "repeat | no-repeat | space | round" - }, - "-webkit-mask-repeat-y": { - "syntax": "repeat | no-repeat | space | round" - }, - "-webkit-mask-size": { - "syntax": "#" - }, - "-webkit-overflow-scrolling": { - "syntax": "auto | touch" - }, - "-webkit-tap-highlight-color": { - "syntax": "" - }, - "-webkit-text-fill-color": { - "syntax": "" - }, - "-webkit-text-stroke": { - "syntax": " || " - }, - "-webkit-text-stroke-color": { - "syntax": "" - }, - "-webkit-text-stroke-width": { - "syntax": "" - }, - "-webkit-touch-callout": { - "syntax": "default | none" - }, - "-webkit-user-modify": { - "syntax": "read-only | read-write | read-write-plaintext-only" - }, - "-webkit-user-select": { - "syntax": " auto | none | text | all" - }, - "accent-color": { - "syntax": "auto | " - }, - "align-content": { - "syntax": "normal | | | ? " - }, - "align-items": { - "syntax": "normal | stretch | | [ ? ] | anchor-center" - }, - "align-self": { - "syntax": "auto | normal | stretch | | ? | anchor-center" - }, - "align-tracks": { - "syntax": "[ normal | | | ? ]#" - }, - "alignment-baseline": { - "syntax": " auto | baseline | before-edge | text-before-edge | middle | central | after-edge | text-after-edge | ideographic | alphabetic | hanging | mathematical" - }, - "all": { - "syntax": "initial | inherit | unset | revert | revert-layer" - }, - "anchor-name": { - "syntax": "none | #" - }, - "anchor-scope": { - "syntax": "none | all | #" - }, - "animation": { - "syntax": "#" - }, - "animation-composition": { - "syntax": "#" - }, - "animation-delay": { - "syntax": "