From a6029b01e445df7ce3e9f3e3b045227537b2ced8 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Sun, 27 Jul 2025 18:23:09 +0900 Subject: [PATCH 01/41] Move some functions exports.foo are hoisted, but for improved readability. --- lib/parsers.js | 75 +++++++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/lib/parsers.js b/lib/parsers.js index 8036f39c..a4120b45 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -144,10 +144,40 @@ exports.prepareValue = function prepareValue(value, globalObject = globalThis) { } }; +// Value starts with var() and/or contains var() exports.hasVarFunc = function hasVarFunc(val) { return varRegEx.test(val) || varContainedRegEx.test(val); }; +// Splits value into an array. +// @see https://github.com/asamuzaK/cssColor/blob/main/src/js/util.ts +exports.splitValue = splitValue; + +exports.parseCSS = function parseCSS(val, opt, toObject = false) { + const ast = cssTree.parse(val, opt); + if (toObject) { + return cssTree.toPlainObject(ast); + } + return ast; +}; + +// Returns `false` for custom property and/or var(). +exports.isValidPropertyValue = function isValidPropertyValue(prop, val) { + // cssTree.lexer does not support deprecated system colors + // @see https://github.com/w3c/webref/issues/1519#issuecomment-3120290261 + if (SYS_COLOR.includes(asciiLowercase(val))) { + if (/^(?:-webkit-)?(?:[a-z][a-z\d]*-)*color$/i.test(prop)) { + return true; + } + return false; + } + const ast = exports.parseCSS(val, { + context: "value" + }); + const { error, matched } = cssTree.lexer.matchProperty(prop, ast); + return error === null && matched !== null; +}; + exports.parseNumber = function parseNumber(val, restrictToPositive = false) { if (val === "") { return ""; @@ -432,6 +462,14 @@ exports.parseColor = function parseColor(val) { return exports.parseKeyword(val); }; +// Returns `false` for global values, e.g. "inherit". +exports.isValidColor = function isValidColor(val) { + if (SYS_COLOR.includes(asciiLowercase(val))) { + return true; + } + return isColor(val); +}; + exports.parseImage = function parseImage(val) { if (val === "") { return ""; @@ -513,40 +551,3 @@ exports.parseShorthand = function parseShorthand(val, shorthandFor, preserve = f } return obj; }; - -exports.parseCSS = function parseCSS(val, opt, toObject = false) { - const ast = cssTree.parse(val, opt); - if (toObject) { - return cssTree.toPlainObject(ast); - } - return ast; -}; - -// Returns `false` for custom property and/or var(). -exports.isValidPropertyValue = function isValidPropertyValue(prop, val) { - // cssTree.lexer does not support deprecated system colors - // @see https://github.com/w3c/webref/issues/1519#issuecomment-3120290261 - if (SYS_COLOR.includes(asciiLowercase(val))) { - if (/^(?:-webkit-)?(?:[a-z][a-z\d]*-)*color$/i.test(prop)) { - return true; - } - return false; - } - const ast = exports.parseCSS(val, { - context: "value" - }); - const { error, matched } = cssTree.lexer.matchProperty(prop, ast); - return error === null && matched !== null; -}; - -// Returns `false` for global values, e.g. "inherit". -exports.isValidColor = function isValidColor(val) { - if (SYS_COLOR.includes(asciiLowercase(val))) { - return true; - } - return isColor(val); -}; - -// Splits value into an array. -// @see https://github.com/asamuzaK/cssColor/blob/main/src/js/util.ts -exports.splitValue = splitValue; From 58746930c61fb9cdc724aa7ff063e2067ae2bb9c Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Mon, 28 Jul 2025 05:46:54 +0900 Subject: [PATCH 02/41] Fix CSSStyleDeclaration.js and parsers.js --- lib/CSSStyleDeclaration.js | 10 +++++++--- lib/parsers.js | 16 ++++++++++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/lib/CSSStyleDeclaration.js b/lib/CSSStyleDeclaration.js index c52ca676..c85062f6 100644 --- a/lib/CSSStyleDeclaration.js +++ b/lib/CSSStyleDeclaration.js @@ -534,7 +534,7 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { } else if (isValidPropertyValue(shorthandProp, val)) { parts.push(...splitValue(val)); } - if (!parts.length || parts.length > positions.length || !parts.every(isValid)) { + if (!parts.length || parts.length > positions.length) { return; } parts = parts.map((p) => parser(p)); @@ -580,11 +580,15 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { */ value(prefix, part, val, isValid, parser, positions = []) { val = prepareValue(val, this._global); - if (typeof val !== "string" || !isValid(val)) { + if (typeof val !== "string") { return; } - val = parser(val); const property = `${prefix}-${part}`; + if (isValidPropertyValue(property, val)) { + val = parser(val); + } else { + return; + } this._setProperty(property, val); const combinedPriority = this.getPropertyPriority(prefix); const subparts = []; diff --git a/lib/parsers.js b/lib/parsers.js index a4120b45..d8619a50 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -163,6 +163,9 @@ exports.parseCSS = function parseCSS(val, opt, toObject = false) { // Returns `false` for custom property and/or var(). exports.isValidPropertyValue = function isValidPropertyValue(prop, val) { + if (val === "") { + return true; + } // cssTree.lexer does not support deprecated system colors // @see https://github.com/w3c/webref/issues/1519#issuecomment-3120290261 if (SYS_COLOR.includes(asciiLowercase(val))) { @@ -171,9 +174,14 @@ exports.isValidPropertyValue = function isValidPropertyValue(prop, val) { } return false; } - const ast = exports.parseCSS(val, { - context: "value" - }); + let ast; + try { + ast = exports.parseCSS(val, { + context: "value" + }); + } catch { + return false; + } const { error, matched } = cssTree.lexer.matchProperty(prop, ast); return error === null && matched !== null; }; @@ -536,7 +544,7 @@ exports.parseShorthand = function parseShorthand(val, shorthandFor, preserve = f let partValid = false; for (let i = 0; i < shorthandArr.length; i++) { const [property, value] = shorthandArr[i]; - if (value.isValid(part)) { + if (exports.isValidPropertyValue(property, part)) { partValid = true; obj[property] = value.parse(part); if (!preserve) { From 5bc765499077f96c1e74474ff85632bd5e445095 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Mon, 28 Jul 2025 05:47:50 +0900 Subject: [PATCH 03/41] Remove isValid() from background --- lib/properties/background.js | 4 ++-- lib/properties/backgroundAttachment.js | 7 ------- lib/properties/backgroundClip.js | 7 ------- lib/properties/backgroundColor.js | 7 ------- lib/properties/backgroundImage.js | 7 ------- lib/properties/backgroundOrigin.js | 7 ------- lib/properties/backgroundPosition.js | 7 ------- lib/properties/backgroundRepeat.js | 7 ------- lib/properties/backgroundSize.js | 7 ------- 9 files changed, 2 insertions(+), 58 deletions(-) diff --git a/lib/properties/background.js b/lib/properties/background.js index df896512..a55c226c 100644 --- a/lib/properties/background.js +++ b/lib/properties/background.js @@ -75,7 +75,7 @@ module.exports.parse = function parse(v) { for (const part of parts1) { let partValid = false; for (const [property, value] of module.exports.shorthandFor) { - if (value.isValid(part)) { + if (parsers.isValidPropertyValue(property, part)) { partValid = true; switch (property) { case "background-clip": @@ -131,7 +131,7 @@ module.exports.parse = function parse(v) { for (const part of parts2) { let partValid = false; for (const [property, value] of module.exports.shorthandFor) { - if (value.isValid(part)) { + if (parsers.isValidPropertyValue(property, part)) { partValid = true; switch (property) { case "background-clip": diff --git a/lib/properties/backgroundAttachment.js b/lib/properties/backgroundAttachment.js index 6b19cdef..7dbf977b 100644 --- a/lib/properties/backgroundAttachment.js +++ b/lib/properties/backgroundAttachment.js @@ -24,13 +24,6 @@ module.exports.parse = function parse(v) { } }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/backgroundClip.js b/lib/properties/backgroundClip.js index 31cfe028..e01948c9 100644 --- a/lib/properties/backgroundClip.js +++ b/lib/properties/backgroundClip.js @@ -24,13 +24,6 @@ module.exports.parse = function parse(v) { } }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/backgroundColor.js b/lib/properties/backgroundColor.js index fdb21cba..099ae13c 100644 --- a/lib/properties/backgroundColor.js +++ b/lib/properties/backgroundColor.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "" || typeof parsers.parseKeyword(v) === "string") { - return true; - } - return parsers.isValidColor(v); -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/backgroundImage.js b/lib/properties/backgroundImage.js index 24f12550..7de2fa0f 100644 --- a/lib/properties/backgroundImage.js +++ b/lib/properties/backgroundImage.js @@ -23,13 +23,6 @@ module.exports.parse = function parse(v) { } }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/backgroundOrigin.js b/lib/properties/backgroundOrigin.js index 66c3fbd5..7e12fb28 100644 --- a/lib/properties/backgroundOrigin.js +++ b/lib/properties/backgroundOrigin.js @@ -24,13 +24,6 @@ module.exports.parse = function parse(v) { } }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/backgroundPosition.js b/lib/properties/backgroundPosition.js index 62541589..c91948c5 100644 --- a/lib/properties/backgroundPosition.js +++ b/lib/properties/backgroundPosition.js @@ -151,13 +151,6 @@ module.exports.parse = function parse(v) { } }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/backgroundRepeat.js b/lib/properties/backgroundRepeat.js index 344bafc7..fa173bd4 100644 --- a/lib/properties/backgroundRepeat.js +++ b/lib/properties/backgroundRepeat.js @@ -57,13 +57,6 @@ module.exports.parse = function parse(v) { } }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/backgroundSize.js b/lib/properties/backgroundSize.js index 8cf46496..b1134327 100644 --- a/lib/properties/backgroundSize.js +++ b/lib/properties/backgroundSize.js @@ -55,13 +55,6 @@ module.exports.parse = function parse(v) { } }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); From 83238ce0c2f108b34a56fc8d2821c3e1cc006d09 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Mon, 28 Jul 2025 07:09:35 +0900 Subject: [PATCH 04/41] Fix typo --- lib/properties/borderBottom.js | 12 ++++++------ lib/properties/borderLeft.js | 12 ++++++------ lib/properties/borderRight.js | 12 ++++++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/properties/borderBottom.js b/lib/properties/borderBottom.js index 98a3bcf3..24426734 100644 --- a/lib/properties/borderBottom.js +++ b/lib/properties/borderBottom.js @@ -1,14 +1,14 @@ "use strict"; const parsers = require("../parsers"); -const borderTopWidth = require("./borderTopWidth"); -const borderTopStyle = require("./borderTopStyle"); -const borderTopColor = require("./borderTopColor"); +const borderBottomWidth = require("./borderBottomWidth"); +const borderBottomStyle = require("./borderBottomStyle"); +const borderBottomColor = require("./borderBottomColor"); module.exports.shorthandFor = new Map([ - ["border-bottom-width", borderTopWidth], - ["border-bottom-style", borderTopStyle], - ["border-bottom-color", borderTopColor] + ["border-bottom-width", borderBottomWidth], + ["border-bottom-style", borderBottomStyle], + ["border-bottom-color", borderBottomColor] ]); module.exports.definition = { diff --git a/lib/properties/borderLeft.js b/lib/properties/borderLeft.js index f1168f38..e283dcd6 100644 --- a/lib/properties/borderLeft.js +++ b/lib/properties/borderLeft.js @@ -1,14 +1,14 @@ "use strict"; const parsers = require("../parsers"); -const borderTopWidth = require("./borderTopWidth"); -const borderTopStyle = require("./borderTopStyle"); -const borderTopColor = require("./borderTopColor"); +const borderLeftWidth = require("./borderLeftWidth"); +const borderLeftStyle = require("./borderLeftStyle"); +const borderLeftColor = require("./borderLeftColor"); module.exports.shorthandFor = new Map([ - ["border-left-width", borderTopWidth], - ["border-left-style", borderTopStyle], - ["border-left-color", borderTopColor] + ["border-left-width", borderLeftWidth], + ["border-left-style", borderLeftStyle], + ["border-left-color", borderLeftColor] ]); module.exports.definition = { diff --git a/lib/properties/borderRight.js b/lib/properties/borderRight.js index df46a3b9..4f911910 100644 --- a/lib/properties/borderRight.js +++ b/lib/properties/borderRight.js @@ -1,14 +1,14 @@ "use strict"; const parsers = require("../parsers"); -const borderTopWidth = require("./borderTopWidth"); -const borderTopStyle = require("./borderTopStyle"); -const borderTopColor = require("./borderTopColor"); +const borderRightWidth = require("./borderRightWidth"); +const borderRightStyle = require("./borderRightStyle"); +const borderRightColor = require("./borderRightColor"); module.exports.shorthandFor = new Map([ - ["border-right-width", borderTopWidth], - ["border-right-style", borderTopStyle], - ["border-right-color", borderTopColor] + ["border-right-width", borderRightWidth], + ["border-right-style", borderRightStyle], + ["border-right-color", borderRightColor] ]); module.exports.definition = { From b5a28cbafe3d26aaa8cd0b67c55e712065d315c7 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Mon, 28 Jul 2025 07:14:30 +0900 Subject: [PATCH 05/41] Fix _implicitSetter --- lib/CSSStyleDeclaration.js | 3 +-- lib/properties/borderColor.js | 16 +--------------- lib/properties/borderStyle.js | 16 +--------------- lib/properties/borderWidth.js | 16 +--------------- lib/properties/margin.js | 25 ++----------------------- lib/properties/padding.js | 25 ++----------------------- 6 files changed, 8 insertions(+), 93 deletions(-) diff --git a/lib/CSSStyleDeclaration.js b/lib/CSSStyleDeclaration.js index c85062f6..151b22e6 100644 --- a/lib/CSSStyleDeclaration.js +++ b/lib/CSSStyleDeclaration.js @@ -514,11 +514,10 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { * @param {string} prefix * @param {string} part * @param {string} val - * @param {Function} isValid * @param {Function} parser * @param {Array.} positions */ - value(prefix, part, val, isValid, parser, positions = []) { + value(prefix, part, val, parser, positions = []) { val = prepareValue(val, this._global); if (typeof val !== "string") { return; diff --git a/lib/properties/borderColor.js b/lib/properties/borderColor.js index d15ea7c8..8725ff84 100644 --- a/lib/properties/borderColor.js +++ b/lib/properties/borderColor.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "" || typeof parsers.parseKeyword(v) === "string") { - return true; - } - return parsers.isValidColor(v); -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); @@ -25,14 +18,7 @@ module.exports.definition = { this._setProperty("border-color", v); } else { const positions = ["top", "right", "bottom", "left"]; - this._implicitSetter( - "border", - "color", - v, - module.exports.isValid, - module.exports.parse, - positions - ); + this._implicitSetter("border", "color", v, module.exports.parse, positions); } }, get() { diff --git a/lib/properties/borderStyle.js b/lib/properties/borderStyle.js index 612ea75e..37ea08da 100644 --- a/lib/properties/borderStyle.js +++ b/lib/properties/borderStyle.js @@ -18,13 +18,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, keywords); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); @@ -37,14 +30,7 @@ module.exports.definition = { return; } const positions = ["top", "right", "bottom", "left"]; - this._implicitSetter( - "border", - "style", - v, - module.exports.isValid, - module.exports.parse, - positions - ); + this._implicitSetter("border", "style", v, module.exports.parse, positions); }, get() { return this.getPropertyValue("border-style"); diff --git a/lib/properties/borderWidth.js b/lib/properties/borderWidth.js index 4a570d95..cf4ea261 100644 --- a/lib/properties/borderWidth.js +++ b/lib/properties/borderWidth.js @@ -11,13 +11,6 @@ module.exports.parse = function parse(v) { return parsers.parseLength(v, true); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); @@ -26,14 +19,7 @@ module.exports.definition = { this._setProperty("border-width", v); } else { const positions = ["top", "right", "bottom", "left"]; - this._implicitSetter( - "border", - "width", - v, - module.exports.isValid, - module.exports.parse, - positions - ); + this._implicitSetter("border", "width", v, module.exports.parse, positions); } }, get() { diff --git a/lib/properties/margin.js b/lib/properties/margin.js index 61f9cc7f..84f02c53 100644 --- a/lib/properties/margin.js +++ b/lib/properties/margin.js @@ -12,35 +12,14 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, ["auto"]); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._implicitSetter( - "margin", - "", - "", - module.exports.isValid, - module.exports.parse, - positions - ); + this._implicitSetter("margin", "", "", module.exports.parse, positions); this._setProperty("margin", v); } else { - this._implicitSetter( - "margin", - "", - v, - module.exports.isValid, - module.exports.parse, - positions - ); + this._implicitSetter("margin", "", v, module.exports.parse, positions); } }, get() { diff --git a/lib/properties/padding.js b/lib/properties/padding.js index 79b736ab..7ce3b1de 100644 --- a/lib/properties/padding.js +++ b/lib/properties/padding.js @@ -12,35 +12,14 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._implicitSetter( - "padding", - "", - "", - module.exports.isValid, - module.exports.parse, - positions - ); + this._implicitSetter("padding", "", "", module.exports.parse, positions); this._setProperty("padding", v); } else { - this._implicitSetter( - "padding", - "", - v, - module.exports.isValid, - module.exports.parse, - positions - ); + this._implicitSetter("padding", "", v, module.exports.parse, positions); } }, get() { From cd9374f83808095930878d0674dc0c3af0733798 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Mon, 28 Jul 2025 07:42:27 +0900 Subject: [PATCH 06/41] Fix _subImplicitSetter --- lib/CSSStyleDeclaration.js | 3 +-- lib/properties/marginBottom.js | 9 +-------- lib/properties/marginLeft.js | 9 +-------- lib/properties/marginRight.js | 9 +-------- lib/properties/marginTop.js | 9 +-------- lib/properties/paddingBottom.js | 19 +++++-------------- lib/properties/paddingLeft.js | 9 +-------- lib/properties/paddingRight.js | 9 +-------- lib/properties/paddingTop.js | 9 +-------- 9 files changed, 13 insertions(+), 72 deletions(-) diff --git a/lib/CSSStyleDeclaration.js b/lib/CSSStyleDeclaration.js index 151b22e6..b0d4efc9 100644 --- a/lib/CSSStyleDeclaration.js +++ b/lib/CSSStyleDeclaration.js @@ -573,11 +573,10 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { * @param {string} prefix * @param {string} part * @param {string} val - * @param {Function} isValid * @param {Function} parser * @param {Array.} positions */ - value(prefix, part, val, isValid, parser, positions = []) { + value(prefix, part, val, parser, positions = []) { val = prepareValue(val, this._global); if (typeof val !== "string") { return; diff --git a/lib/properties/marginBottom.js b/lib/properties/marginBottom.js index 8e276d56..b41c4345 100644 --- a/lib/properties/marginBottom.js +++ b/lib/properties/marginBottom.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, ["auto"]); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); @@ -24,7 +17,7 @@ module.exports.definition = { this._setProperty("margin", ""); this._setProperty("margin-bottom", v); } else { - this._subImplicitSetter("margin", "bottom", v, module.exports.isValid, module.exports.parse, [ + this._subImplicitSetter("margin", "bottom", v, module.exports.parse, [ "top", "right", "bottom", diff --git a/lib/properties/marginLeft.js b/lib/properties/marginLeft.js index f50a2be1..34930028 100644 --- a/lib/properties/marginLeft.js +++ b/lib/properties/marginLeft.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, ["auto"]); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); @@ -24,7 +17,7 @@ module.exports.definition = { this._setProperty("margin", ""); this._setProperty("margin-left", v); } else { - this._subImplicitSetter("margin", "left", v, module.exports.isValid, module.exports.parse, [ + this._subImplicitSetter("margin", "left", v, module.exports.parse, [ "top", "right", "bottom", diff --git a/lib/properties/marginRight.js b/lib/properties/marginRight.js index b1de3518..437bc808 100644 --- a/lib/properties/marginRight.js +++ b/lib/properties/marginRight.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, ["auto"]); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); @@ -24,7 +17,7 @@ module.exports.definition = { this._setProperty("margin", ""); this._setProperty("margin-right", v); } else { - this._subImplicitSetter("margin", "right", v, module.exports.isValid, module.exports.parse, [ + this._subImplicitSetter("margin", "right", v, module.exports.parse, [ "top", "right", "bottom", diff --git a/lib/properties/marginTop.js b/lib/properties/marginTop.js index b6d73a7f..dab96396 100644 --- a/lib/properties/marginTop.js +++ b/lib/properties/marginTop.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, ["auto"]); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); @@ -24,7 +17,7 @@ module.exports.definition = { this._setProperty("margin", ""); this._setProperty("margin-top", v); } else { - this._subImplicitSetter("margin", "top", v, module.exports.isValid, module.exports.parse, [ + this._subImplicitSetter("margin", "top", v, module.exports.parse, [ "top", "right", "bottom", diff --git a/lib/properties/paddingBottom.js b/lib/properties/paddingBottom.js index b0b0ae91..7de58d06 100644 --- a/lib/properties/paddingBottom.js +++ b/lib/properties/paddingBottom.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); @@ -24,14 +17,12 @@ module.exports.definition = { this._setProperty("padding", ""); this._setProperty("padding-bottom", v); } else { - this._subImplicitSetter( - "padding", + this._subImplicitSetter("padding", "bottom", v, module.exports.parse, [ + "top", + "right", "bottom", - v, - module.exports.isValid, - module.exports.parse, - ["top", "right", "bottom", "left"] - ); + "left" + ]); } }, get() { diff --git a/lib/properties/paddingLeft.js b/lib/properties/paddingLeft.js index 5a280647..046f513d 100644 --- a/lib/properties/paddingLeft.js +++ b/lib/properties/paddingLeft.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); @@ -24,7 +17,7 @@ module.exports.definition = { this._setProperty("padding", ""); this._setProperty("padding-left", v); } else { - this._subImplicitSetter("padding", "left", v, module.exports.isValid, module.exports.parse, [ + this._subImplicitSetter("padding", "left", v, module.exports.parse, [ "top", "right", "bottom", diff --git a/lib/properties/paddingRight.js b/lib/properties/paddingRight.js index 265474a3..7109933a 100644 --- a/lib/properties/paddingRight.js +++ b/lib/properties/paddingRight.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); @@ -24,7 +17,7 @@ module.exports.definition = { this._setProperty("padding", ""); this._setProperty("padding-right", v); } else { - this._subImplicitSetter("padding", "right", v, module.exports.isValid, module.exports.parse, [ + this._subImplicitSetter("padding", "right", v, module.exports.parse, [ "top", "right", "bottom", diff --git a/lib/properties/paddingTop.js b/lib/properties/paddingTop.js index b0603b77..b12a19f2 100644 --- a/lib/properties/paddingTop.js +++ b/lib/properties/paddingTop.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); @@ -24,7 +17,7 @@ module.exports.definition = { this._setProperty("padding", ""); this._setProperty("padding-top", v); } else { - this._subImplicitSetter("padding", "top", v, module.exports.isValid, module.exports.parse, [ + this._subImplicitSetter("padding", "top", v, module.exports.parse, [ "top", "right", "bottom", From 4f3f22e2486c5898d1aeb6890228152c9aea1b76 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Thu, 31 Jul 2025 05:02:18 +0900 Subject: [PATCH 07/41] Remove isValid() --- lib/properties/borderBottomColor.js | 7 ------ lib/properties/borderBottomStyle.js | 7 ------ lib/properties/borderBottomWidth.js | 7 ------ lib/properties/borderCollapse.js | 7 ------ lib/properties/borderLeftColor.js | 7 ------ lib/properties/borderLeftStyle.js | 7 ------ lib/properties/borderLeftWidth.js | 7 ------ lib/properties/borderRightColor.js | 7 ------ lib/properties/borderRightStyle.js | 7 ------ lib/properties/borderRightWidth.js | 7 ------ lib/properties/borderSpacing.js | 7 ------ lib/properties/borderTopColor.js | 7 ------ lib/properties/borderTopStyle.js | 7 ------ lib/properties/borderTopWidth.js | 7 ------ lib/properties/bottom.js | 7 ------ lib/properties/clear.js | 7 ------ lib/properties/clip.js | 7 ------ lib/properties/color.js | 7 ------ lib/properties/display.js | 7 ------ lib/properties/flex.js | 7 ------ lib/properties/flexBasis.js | 4 ---- lib/properties/flexGrow.js | 4 ---- lib/properties/flexShrink.js | 4 ---- lib/properties/float.js | 7 ------ lib/properties/floodColor.js | 7 ------ lib/properties/font.js | 28 ++++++++++++++--------- lib/properties/fontFamily.js | 7 ------ lib/properties/fontSize.js | 7 ------ lib/properties/fontStyle.js | 7 ------ lib/properties/fontVariant.js | 7 ------ lib/properties/fontWeight.js | 7 ------ lib/properties/height.js | 7 ------ lib/properties/left.js | 7 ------ lib/properties/lightingColor.js | 7 ------ lib/properties/lineHeight.js | 7 ------ lib/properties/opacity.js | 7 ------ lib/properties/outlineColor.js | 7 ------ lib/properties/right.js | 7 ------ lib/properties/stopColor.js | 7 ------ lib/properties/top.js | 7 ------ lib/properties/webkitBorderAfterColor.js | 7 ------ lib/properties/webkitBorderBeforeColor.js | 7 ------ lib/properties/webkitBorderEndColor.js | 7 ------ lib/properties/webkitBorderStartColor.js | 7 ------ lib/properties/webkitColumnRuleColor.js | 7 ------ lib/properties/webkitTapHighlightColor.js | 7 ------ lib/properties/webkitTextEmphasisColor.js | 7 ------ lib/properties/webkitTextFillColor.js | 7 ------ lib/properties/webkitTextStrokeColor.js | 7 ------ lib/properties/width.js | 7 ------ 50 files changed, 17 insertions(+), 345 deletions(-) diff --git a/lib/properties/borderBottomColor.js b/lib/properties/borderBottomColor.js index 5aef19a6..5452b553 100644 --- a/lib/properties/borderBottomColor.js +++ b/lib/properties/borderBottomColor.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "" || typeof parsers.parseKeyword(v) === "string") { - return true; - } - return parsers.isValidColor(v); -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/borderBottomStyle.js b/lib/properties/borderBottomStyle.js index 5cd1ef13..8b2c1a2e 100644 --- a/lib/properties/borderBottomStyle.js +++ b/lib/properties/borderBottomStyle.js @@ -18,13 +18,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, keywords); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/borderBottomWidth.js b/lib/properties/borderBottomWidth.js index 905f08d5..8b92169d 100644 --- a/lib/properties/borderBottomWidth.js +++ b/lib/properties/borderBottomWidth.js @@ -11,13 +11,6 @@ module.exports.parse = function parse(v) { return parsers.parseLength(v, true); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/borderCollapse.js b/lib/properties/borderCollapse.js index 992479f0..24ab25f5 100644 --- a/lib/properties/borderCollapse.js +++ b/lib/properties/borderCollapse.js @@ -6,13 +6,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, ["collapse", "separate"]); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/borderLeftColor.js b/lib/properties/borderLeftColor.js index c1ae6d94..891dcb1c 100644 --- a/lib/properties/borderLeftColor.js +++ b/lib/properties/borderLeftColor.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "" || typeof parsers.parseKeyword(v) === "string") { - return true; - } - return parsers.isValidColor(v); -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/borderLeftStyle.js b/lib/properties/borderLeftStyle.js index de2fc2fc..e0378f0b 100644 --- a/lib/properties/borderLeftStyle.js +++ b/lib/properties/borderLeftStyle.js @@ -18,13 +18,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, keywords); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/borderLeftWidth.js b/lib/properties/borderLeftWidth.js index 1a35a9ba..190e1dce 100644 --- a/lib/properties/borderLeftWidth.js +++ b/lib/properties/borderLeftWidth.js @@ -11,13 +11,6 @@ module.exports.parse = function parse(v) { return parsers.parseLength(v, true); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/borderRightColor.js b/lib/properties/borderRightColor.js index 2118d917..281c6457 100644 --- a/lib/properties/borderRightColor.js +++ b/lib/properties/borderRightColor.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "" || typeof parsers.parseKeyword(v) === "string") { - return true; - } - return parsers.isValidColor(v); -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/borderRightStyle.js b/lib/properties/borderRightStyle.js index cbc22df3..4aa78b67 100644 --- a/lib/properties/borderRightStyle.js +++ b/lib/properties/borderRightStyle.js @@ -18,13 +18,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, keywords); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/borderRightWidth.js b/lib/properties/borderRightWidth.js index 880d0111..8c82f623 100644 --- a/lib/properties/borderRightWidth.js +++ b/lib/properties/borderRightWidth.js @@ -11,13 +11,6 @@ module.exports.parse = function parse(v) { return parsers.parseLength(v, true); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/borderSpacing.js b/lib/properties/borderSpacing.js index 3eccff04..b3b3aac3 100644 --- a/lib/properties/borderSpacing.js +++ b/lib/properties/borderSpacing.js @@ -25,13 +25,6 @@ module.exports.parse = function parse(v) { return val.join(" "); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/borderTopColor.js b/lib/properties/borderTopColor.js index b7897fca..ce6a111a 100644 --- a/lib/properties/borderTopColor.js +++ b/lib/properties/borderTopColor.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "" || typeof parsers.parseKeyword(v) === "string") { - return true; - } - return parsers.isValidColor(v); -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/borderTopStyle.js b/lib/properties/borderTopStyle.js index 6341d33d..49cfdefd 100644 --- a/lib/properties/borderTopStyle.js +++ b/lib/properties/borderTopStyle.js @@ -18,13 +18,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, keywords); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/borderTopWidth.js b/lib/properties/borderTopWidth.js index b3e7366b..c99f6983 100644 --- a/lib/properties/borderTopWidth.js +++ b/lib/properties/borderTopWidth.js @@ -11,13 +11,6 @@ module.exports.parse = function parse(v) { return parsers.parseLength(v, true); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/bottom.js b/lib/properties/bottom.js index 202025ba..bb4059a6 100644 --- a/lib/properties/bottom.js +++ b/lib/properties/bottom.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, ["auto"]); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/clear.js b/lib/properties/clear.js index 8570fea0..23b835b9 100644 --- a/lib/properties/clear.js +++ b/lib/properties/clear.js @@ -20,13 +20,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, keywords); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/clip.js b/lib/properties/clip.js index 13fd9336..edf359fd 100644 --- a/lib/properties/clip.js +++ b/lib/properties/clip.js @@ -34,13 +34,6 @@ module.exports.parse = function parse(v) { return `rect(${parts.join(", ")})`; }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/color.js b/lib/properties/color.js index dcd0e090..86a7be74 100644 --- a/lib/properties/color.js +++ b/lib/properties/color.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "" || typeof parsers.parseKeyword(v) === "string") { - return true; - } - return parsers.isValidColor(v); -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/display.js b/lib/properties/display.js index 95db3caa..9e17ffa2 100644 --- a/lib/properties/display.js +++ b/lib/properties/display.js @@ -197,13 +197,6 @@ module.exports.parse = function parse(v) { } }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/flex.js b/lib/properties/flex.js index 233710ef..aa94ba0a 100644 --- a/lib/properties/flex.js +++ b/lib/properties/flex.js @@ -40,13 +40,6 @@ module.exports.parse = function parse(v) { } }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/flexBasis.js b/lib/properties/flexBasis.js index bcc4ab75..1c9d99ec 100644 --- a/lib/properties/flexBasis.js +++ b/lib/properties/flexBasis.js @@ -11,10 +11,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, keywords); }; -module.exports.isValid = function isValid(v) { - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/flexGrow.js b/lib/properties/flexGrow.js index cd75d2ac..e1be6754 100644 --- a/lib/properties/flexGrow.js +++ b/lib/properties/flexGrow.js @@ -6,10 +6,6 @@ module.exports.parse = function parse(v) { return parsers.parseNumber(v, true); }; -module.exports.isValid = function isValid(v) { - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/flexShrink.js b/lib/properties/flexShrink.js index c2b023c4..c9d9dab7 100644 --- a/lib/properties/flexShrink.js +++ b/lib/properties/flexShrink.js @@ -6,10 +6,6 @@ module.exports.parse = function parse(v) { return parsers.parseNumber(v, true); }; -module.exports.isValid = function isValid(v) { - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/float.js b/lib/properties/float.js index 25476e34..a2622691 100644 --- a/lib/properties/float.js +++ b/lib/properties/float.js @@ -7,13 +7,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, keywords); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/floodColor.js b/lib/properties/floodColor.js index c0398005..8f84bf91 100644 --- a/lib/properties/floodColor.js +++ b/lib/properties/floodColor.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "" || typeof parsers.parseKeyword(v) === "string") { - return true; - } - return parsers.isValidColor(v); -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/font.js b/lib/properties/font.js index 55b93844..8002f0b2 100644 --- a/lib/properties/font.js +++ b/lib/properties/font.js @@ -37,12 +37,12 @@ module.exports.parse = function parse(v) { const fontFamilies = new Set(); if (fontBlockB) { const [lineB, ...familiesB] = fontBlockB.trim().split(" "); - if (!lineB || !lineHeight.isValid(lineB) || !familiesB.length) { + if (!lineB || !parsers.isValidPropertyValue("line-height", lineB) || !familiesB.length) { return; } const lineHeightB = lineHeight.parse(lineB); const familyB = familiesB.join(" "); - if (fontFamily.isValid(familyB)) { + if (parsers.isValidPropertyValue("font-family", familyB)) { fontFamilies.add(fontFamily.parse(familyB)); } else { return; @@ -57,10 +57,16 @@ module.exports.parse = function parse(v) { switch (property) { case "font-style": case "font-variant": - case "font-weight": + case "font-weight": { + if (font[property] === "normal" && parsers.isValidPropertyValue(property, part)) { + const value = module.exports.shorthandFor.get(property); + font[property] = value.parse(part); + } + break; + } case "font-size": { - const value = module.exports.shorthandFor.get(property); - if (value.isValid(part)) { + if (parsers.isValidPropertyValue(property, part)) { + const value = module.exports.shorthandFor.get(property); font[property] = value.parse(part); } break; @@ -95,8 +101,8 @@ module.exports.parse = function parse(v) { case "font-variant": case "font-weight": case "line-height": { - const value = module.exports.shorthandFor.get(property); - if (value.isValid(part)) { + if (parsers.isValidPropertyValue(property, part)) { + const value = module.exports.shorthandFor.get(property); font[property] = value.parse(part); } break; @@ -105,16 +111,16 @@ module.exports.parse = function parse(v) { } } } - } else if (fontSize.isValid(part)) { + } else if (parsers.isValidPropertyValue("font-size", part)) { fontSizeA = fontSize.parse(part); - } else if (fontFamily.isValid(part)) { + } else if (parsers.isValidPropertyValue("font-family", part)) { revFontFamily.push(part); } else { return; } } const family = revFontFamily.reverse().join(" "); - if (fontSizeA && fontFamily.isValid(family)) { + if (fontSizeA && parsers.isValidPropertyValue("font-family", family)) { font["font-size"] = fontSizeA; fontFamilies.add(fontFamily.parse(family)); } else { @@ -122,7 +128,7 @@ module.exports.parse = function parse(v) { } } for (const family of families) { - if (fontFamily.isValid(family)) { + if (parsers.isValidPropertyValue("font-family", family)) { fontFamilies.add(fontFamily.parse(family)); } else { return; diff --git a/lib/properties/fontFamily.js b/lib/properties/fontFamily.js index 4516abbc..7df55a8b 100644 --- a/lib/properties/fontFamily.js +++ b/lib/properties/fontFamily.js @@ -70,13 +70,6 @@ module.exports.parse = function parse(v) { return font.join(", "); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/fontSize.js b/lib/properties/fontSize.js index f07a8627..482990eb 100644 --- a/lib/properties/fontSize.js +++ b/lib/properties/fontSize.js @@ -22,13 +22,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, keywords); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/fontStyle.js b/lib/properties/fontStyle.js index 0c5e898f..be72332c 100644 --- a/lib/properties/fontStyle.js +++ b/lib/properties/fontStyle.js @@ -7,13 +7,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, keywords); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/fontVariant.js b/lib/properties/fontVariant.js index 919a11c9..39b0b057 100644 --- a/lib/properties/fontVariant.js +++ b/lib/properties/fontVariant.js @@ -11,13 +11,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, keywords); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/fontWeight.js b/lib/properties/fontWeight.js index 0c4abbad..29f90502 100644 --- a/lib/properties/fontWeight.js +++ b/lib/properties/fontWeight.js @@ -11,13 +11,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, keywords); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/height.js b/lib/properties/height.js index b99227b8..40f4f4e6 100644 --- a/lib/properties/height.js +++ b/lib/properties/height.js @@ -11,13 +11,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, keywords); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/left.js b/lib/properties/left.js index 91770a77..531fb74e 100644 --- a/lib/properties/left.js +++ b/lib/properties/left.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, ["auto"]); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/lightingColor.js b/lib/properties/lightingColor.js index 08718ff9..3357643e 100644 --- a/lib/properties/lightingColor.js +++ b/lib/properties/lightingColor.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "" || typeof parsers.parseKeyword(v) === "string") { - return true; - } - return parsers.isValidColor(v); -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/lineHeight.js b/lib/properties/lineHeight.js index 3b2312d8..c56aa9e9 100644 --- a/lib/properties/lineHeight.js +++ b/lib/properties/lineHeight.js @@ -14,13 +14,6 @@ module.exports.parse = function parse(v) { return parsers.parseMeasurement(v, true); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/opacity.js b/lib/properties/opacity.js index d5191754..2479e7e6 100644 --- a/lib/properties/opacity.js +++ b/lib/properties/opacity.js @@ -26,13 +26,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/outlineColor.js b/lib/properties/outlineColor.js index d5f52dcd..0d240c5e 100644 --- a/lib/properties/outlineColor.js +++ b/lib/properties/outlineColor.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "" || typeof parsers.parseKeyword(v) === "string") { - return true; - } - return parsers.isValidColor(v); -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/right.js b/lib/properties/right.js index 41132a15..06bfab10 100644 --- a/lib/properties/right.js +++ b/lib/properties/right.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, ["auto"]); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/stopColor.js b/lib/properties/stopColor.js index 08fd7c5a..5e781aea 100644 --- a/lib/properties/stopColor.js +++ b/lib/properties/stopColor.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "" || typeof parsers.parseKeyword(v) === "string") { - return true; - } - return parsers.isValidColor(v); -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/top.js b/lib/properties/top.js index c55f7bd6..051d3c2f 100644 --- a/lib/properties/top.js +++ b/lib/properties/top.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, ["auto"]); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/webkitBorderAfterColor.js b/lib/properties/webkitBorderAfterColor.js index 1159f065..47d63b18 100644 --- a/lib/properties/webkitBorderAfterColor.js +++ b/lib/properties/webkitBorderAfterColor.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "" || typeof parsers.parseKeyword(v) === "string") { - return true; - } - return parsers.isValidColor(v); -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/webkitBorderBeforeColor.js b/lib/properties/webkitBorderBeforeColor.js index 3ae008ba..204e04c9 100644 --- a/lib/properties/webkitBorderBeforeColor.js +++ b/lib/properties/webkitBorderBeforeColor.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "" || typeof parsers.parseKeyword(v) === "string") { - return true; - } - return parsers.isValidColor(v); -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/webkitBorderEndColor.js b/lib/properties/webkitBorderEndColor.js index 878b3cc5..033ece75 100644 --- a/lib/properties/webkitBorderEndColor.js +++ b/lib/properties/webkitBorderEndColor.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "" || typeof parsers.parseKeyword(v) === "string") { - return true; - } - return parsers.isValidColor(v); -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/webkitBorderStartColor.js b/lib/properties/webkitBorderStartColor.js index e26d48a6..01e3c81f 100644 --- a/lib/properties/webkitBorderStartColor.js +++ b/lib/properties/webkitBorderStartColor.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "" || typeof parsers.parseKeyword(v) === "string") { - return true; - } - return parsers.isValidColor(v); -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/webkitColumnRuleColor.js b/lib/properties/webkitColumnRuleColor.js index cd64aeb8..df16e52f 100644 --- a/lib/properties/webkitColumnRuleColor.js +++ b/lib/properties/webkitColumnRuleColor.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "" || typeof parsers.parseKeyword(v) === "string") { - return true; - } - return parsers.isValidColor(v); -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/webkitTapHighlightColor.js b/lib/properties/webkitTapHighlightColor.js index 5c2cef12..d219ed96 100644 --- a/lib/properties/webkitTapHighlightColor.js +++ b/lib/properties/webkitTapHighlightColor.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "" || typeof parsers.parseKeyword(v) === "string") { - return true; - } - return parsers.isValidColor(v); -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/webkitTextEmphasisColor.js b/lib/properties/webkitTextEmphasisColor.js index 58850316..a0adf738 100644 --- a/lib/properties/webkitTextEmphasisColor.js +++ b/lib/properties/webkitTextEmphasisColor.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "" || typeof parsers.parseKeyword(v) === "string") { - return true; - } - return parsers.isValidColor(v); -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/webkitTextFillColor.js b/lib/properties/webkitTextFillColor.js index 9c2934fe..9667dba1 100644 --- a/lib/properties/webkitTextFillColor.js +++ b/lib/properties/webkitTextFillColor.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "" || typeof parsers.parseKeyword(v) === "string") { - return true; - } - return parsers.isValidColor(v); -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/webkitTextStrokeColor.js b/lib/properties/webkitTextStrokeColor.js index 6558eac4..e3486e33 100644 --- a/lib/properties/webkitTextStrokeColor.js +++ b/lib/properties/webkitTextStrokeColor.js @@ -10,13 +10,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v); }; -module.exports.isValid = function isValid(v) { - if (v === "" || typeof parsers.parseKeyword(v) === "string") { - return true; - } - return parsers.isValidColor(v); -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); diff --git a/lib/properties/width.js b/lib/properties/width.js index 75940beb..28c1fe0d 100644 --- a/lib/properties/width.js +++ b/lib/properties/width.js @@ -11,13 +11,6 @@ module.exports.parse = function parse(v) { return parsers.parseKeyword(v, keywords); }; -module.exports.isValid = function isValid(v) { - if (v === "") { - return true; - } - return typeof module.exports.parse(v) === "string"; -}; - module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); From 3733d9aae66109ba37e6b0cc3c852d6cfa3e7724 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Thu, 31 Jul 2025 05:09:29 +0900 Subject: [PATCH 08/41] Remove isValidColor() --- lib/parsers.js | 10 +--------- test/parsers.test.js | 37 ------------------------------------- 2 files changed, 1 insertion(+), 46 deletions(-) diff --git a/lib/parsers.js b/lib/parsers.js index d8619a50..4c0b7193 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -6,7 +6,7 @@ const { resolve: resolveColor, - utils: { cssCalc, isColor, resolveGradient, splitValue } + utils: { cssCalc, resolveGradient, splitValue } } = require("@asamuzakjp/css-color"); const { next: syntaxes } = require("@csstools/css-syntax-patches-for-csstree"); const csstree = require("css-tree"); @@ -470,14 +470,6 @@ exports.parseColor = function parseColor(val) { return exports.parseKeyword(val); }; -// Returns `false` for global values, e.g. "inherit". -exports.isValidColor = function isValidColor(val) { - if (SYS_COLOR.includes(asciiLowercase(val))) { - return true; - } - return isColor(val); -}; - exports.parseImage = function parseImage(val) { if (val === "") { return ""; diff --git a/test/parsers.test.js b/test/parsers.test.js index 371fb88d..9f5b4635 100644 --- a/test/parsers.test.js +++ b/test/parsers.test.js @@ -1262,40 +1262,3 @@ describe("isValidPropertyValue", () => { assert.strictEqual(output, false); }); }); - -describe("isValidColor", () => { - it("should return false", () => { - const input = "foo"; - const output = parsers.isValidColor(input); - - assert.strictEqual(output, false); - }); - - it("should return false", () => { - const input = "inherit"; - const output = parsers.isValidColor(input); - - assert.strictEqual(output, false); - }); - - it("should return true", () => { - const input = "green"; - const output = parsers.isValidColor(input); - - assert.strictEqual(output, true); - }); - - it("should return true", () => { - const input = "#008000"; - const output = parsers.isValidColor(input); - - assert.strictEqual(output, true); - }); - - it("should return true", () => { - const input = "rgb(0 128 0)"; - const output = parsers.isValidColor(input); - - assert.strictEqual(output, true); - }); -}); From 1c08f53c29d4bcf0ffd22926d90aa60758d33c8f Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Fri, 1 Aug 2025 08:53:28 +0900 Subject: [PATCH 09/41] Update properties --- lib/properties/borderBottomColor.js | 6 ++++-- lib/properties/borderBottomStyle.js | 18 ++++++++++-------- lib/properties/borderBottomWidth.js | 6 ++++-- lib/properties/borderCollapse.js | 7 ++++++- lib/properties/borderLeftColor.js | 6 ++++-- lib/properties/borderLeftStyle.js | 18 ++++++++++-------- lib/properties/borderLeftWidth.js | 6 ++++-- lib/properties/borderRightColor.js | 6 ++++-- lib/properties/borderRightStyle.js | 18 ++++++++++-------- lib/properties/borderRightWidth.js | 6 ++++-- lib/properties/borderSpacing.js | 7 ++++++- lib/properties/borderStyle.js | 12 ++++++------ lib/properties/borderTopColor.js | 6 ++++-- lib/properties/borderTopStyle.js | 18 ++++++++++-------- lib/properties/borderTopWidth.js | 6 ++++-- lib/properties/bottom.js | 6 +++++- lib/properties/clear.js | 6 +++++- lib/properties/clip.js | 6 +++++- lib/properties/color.js | 6 +++++- lib/properties/display.js | 6 +++++- lib/properties/floodColor.js | 6 +++++- lib/properties/height.js | 6 +++++- lib/properties/left.js | 6 +++++- lib/properties/lightingColor.js | 6 +++++- lib/properties/opacity.js | 6 +++++- lib/properties/outlineColor.js | 6 +++++- lib/properties/right.js | 6 +++++- lib/properties/stopColor.js | 6 +++++- lib/properties/top.js | 6 +++++- lib/properties/width.js | 6 +++++- test/CSSStyleDeclaration.test.js | 20 ++++++++++---------- 31 files changed, 175 insertions(+), 81 deletions(-) diff --git a/lib/properties/borderBottomColor.js b/lib/properties/borderBottomColor.js index 5452b553..686a5cae 100644 --- a/lib/properties/borderBottomColor.js +++ b/lib/properties/borderBottomColor.js @@ -15,10 +15,12 @@ module.exports.definition = { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { this._setProperty("border", ""); - this._setProperty("border-bottom", ""); this._setProperty("border-color", ""); + this._setProperty("border-bottom", ""); + this._setProperty("border-bottom-color", v); + } else { + this._setProperty("border-bottom-color", module.exports.parse(v)); } - this._setProperty("border-bottom-color", module.exports.parse(v)); }, get() { return this.getPropertyValue("border-bottom-color"); diff --git a/lib/properties/borderBottomStyle.js b/lib/properties/borderBottomStyle.js index 8b2c1a2e..03f19174 100644 --- a/lib/properties/borderBottomStyle.js +++ b/lib/properties/borderBottomStyle.js @@ -21,19 +21,21 @@ module.exports.parse = function parse(v) { module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - const val = module.exports.parse(v); - if (val === "none" || val === "hidden") { - this._setProperty("border-bottom-style", ""); - this._setProperty("border-bottom-color", ""); - this._setProperty("border-bottom-width", ""); - return; - } if (parsers.hasVarFunc(v)) { this._setProperty("border", ""); this._setProperty("border-bottom", ""); this._setProperty("border-style", ""); + this._setProperty("border-bottom-style", v); + } else { + const val = module.exports.parse(v); + if (val === "" || val === "none" || val === "hidden") { + this._setProperty("border-bottom-style", ""); + this._setProperty("border-bottom-color", ""); + this._setProperty("border-bottom-width", ""); + } else { + this._setProperty("border-bottom-style", val); + } } - this._setProperty("border-bottom-style", val); }, get() { return this.getPropertyValue("border-bottom-style"); diff --git a/lib/properties/borderBottomWidth.js b/lib/properties/borderBottomWidth.js index 8b92169d..0bea5f63 100644 --- a/lib/properties/borderBottomWidth.js +++ b/lib/properties/borderBottomWidth.js @@ -16,10 +16,12 @@ module.exports.definition = { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { this._setProperty("border", ""); - this._setProperty("border-bottom", ""); this._setProperty("border-width", ""); + this._setProperty("border-bottom", ""); + this._setProperty("border-bottom-width", v); + } else { + this._setProperty("border-bottom-width", module.exports.parse(v)); } - this._setProperty("border-bottom-width", module.exports.parse(v)); }, get() { return this.getPropertyValue("border-bottom-width"); diff --git a/lib/properties/borderCollapse.js b/lib/properties/borderCollapse.js index 24ab25f5..1aed0f64 100644 --- a/lib/properties/borderCollapse.js +++ b/lib/properties/borderCollapse.js @@ -9,7 +9,12 @@ module.exports.parse = function parse(v) { module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("border-collapse", module.exports.parse(v)); + if (parsers.hasVarFunc(v)) { + this._setProperty("border", ""); + this._setProperty("border-collapse", v); + } else { + this._setProperty("border-collapse", module.exports.parse(v)); + } }, get() { return this.getPropertyValue("border-collapse"); diff --git a/lib/properties/borderLeftColor.js b/lib/properties/borderLeftColor.js index 891dcb1c..7816ab77 100644 --- a/lib/properties/borderLeftColor.js +++ b/lib/properties/borderLeftColor.js @@ -15,10 +15,12 @@ module.exports.definition = { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { this._setProperty("border", ""); - this._setProperty("border-left", ""); this._setProperty("border-color", ""); + this._setProperty("border-left", ""); + this._setProperty("border-left-color", v); + } else { + this._setProperty("border-left-color", module.exports.parse(v)); } - this._setProperty("border-left-color", module.exports.parse(v)); }, get() { return this.getPropertyValue("border-left-color"); diff --git a/lib/properties/borderLeftStyle.js b/lib/properties/borderLeftStyle.js index e0378f0b..e82aa7a6 100644 --- a/lib/properties/borderLeftStyle.js +++ b/lib/properties/borderLeftStyle.js @@ -21,19 +21,21 @@ module.exports.parse = function parse(v) { module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - const val = module.exports.parse(v); - if (val === "none" || val === "hidden") { - this._setProperty("border-left-style", ""); - this._setProperty("border-left-color", ""); - this._setProperty("border-left-width", ""); - return; - } if (parsers.hasVarFunc(v)) { this._setProperty("border", ""); this._setProperty("border-left", ""); this._setProperty("border-style", ""); + this._setProperty("border-left-style", v); + } else { + const val = module.exports.parse(v); + if (val === "" || val === "none" || val === "hidden") { + this._setProperty("border-left-style", ""); + this._setProperty("border-left-color", ""); + this._setProperty("border-left-width", ""); + } else { + this._setProperty("border-left-style", val); + } } - this._setProperty("border-left-style", val); }, get() { return this.getPropertyValue("border-left-style"); diff --git a/lib/properties/borderLeftWidth.js b/lib/properties/borderLeftWidth.js index 190e1dce..3038c16b 100644 --- a/lib/properties/borderLeftWidth.js +++ b/lib/properties/borderLeftWidth.js @@ -16,10 +16,12 @@ module.exports.definition = { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { this._setProperty("border", ""); - this._setProperty("border-left", ""); this._setProperty("border-width", ""); + this._setProperty("border-left", ""); + this._setProperty("border-left-width", v); + } else { + this._setProperty("border-left-width", module.exports.parse(v)); } - this._setProperty("border-left-width", module.exports.parse(v)); }, get() { return this.getPropertyValue("border-left-width"); diff --git a/lib/properties/borderRightColor.js b/lib/properties/borderRightColor.js index 281c6457..61c55802 100644 --- a/lib/properties/borderRightColor.js +++ b/lib/properties/borderRightColor.js @@ -15,10 +15,12 @@ module.exports.definition = { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { this._setProperty("border", ""); - this._setProperty("border-right", ""); this._setProperty("border-color", ""); + this._setProperty("border-right", ""); + this._setProperty("border-right-color", v); + } else { + this._setProperty("border-right-color", module.exports.parse(v)); } - this._setProperty("border-right-color", module.exports.parse(v)); }, get() { return this.getPropertyValue("border-right-color"); diff --git a/lib/properties/borderRightStyle.js b/lib/properties/borderRightStyle.js index 4aa78b67..6caba8cc 100644 --- a/lib/properties/borderRightStyle.js +++ b/lib/properties/borderRightStyle.js @@ -21,19 +21,21 @@ module.exports.parse = function parse(v) { module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - const val = module.exports.parse(v); - if (val === "none" || val === "hidden") { - this._setProperty("border-right-style", ""); - this._setProperty("border-right-color", ""); - this._setProperty("border-right-width", ""); - return; - } if (parsers.hasVarFunc(v)) { this._setProperty("border", ""); this._setProperty("border-right", ""); this._setProperty("border-style", ""); + this._setProperty("border-right-style", v); + } else { + const val = module.exports.parse(v); + if (val === "" || val === "none" || val === "hidden") { + this._setProperty("border-right-style", ""); + this._setProperty("border-right-color", ""); + this._setProperty("border-right-width", ""); + } else { + this._setProperty("border-right-style", val); + } } - this._setProperty("border-right-style", val); }, get() { return this.getPropertyValue("border-right-style"); diff --git a/lib/properties/borderRightWidth.js b/lib/properties/borderRightWidth.js index 8c82f623..6a08e27f 100644 --- a/lib/properties/borderRightWidth.js +++ b/lib/properties/borderRightWidth.js @@ -16,10 +16,12 @@ module.exports.definition = { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { this._setProperty("border", ""); - this._setProperty("border-right", ""); this._setProperty("border-width", ""); + this._setProperty("border-right", ""); + this._setProperty("border-right-width", v); + } else { + this._setProperty("border-right-width", module.exports.parse(v)); } - this._setProperty("border-right-width", module.exports.parse(v)); }, get() { return this.getPropertyValue("border-right-width"); diff --git a/lib/properties/borderSpacing.js b/lib/properties/borderSpacing.js index b3b3aac3..3ff5a96e 100644 --- a/lib/properties/borderSpacing.js +++ b/lib/properties/borderSpacing.js @@ -28,7 +28,12 @@ module.exports.parse = function parse(v) { module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("border-spacing", module.exports.parse(v)); + if (parsers.hasVarFunc(v)) { + this._setProperty("border", ""); + this._setProperty("border-spacing", v); + } else { + this._setProperty("border-spacing", module.exports.parse(v)); + } }, get() { return this.getPropertyValue("border-spacing"); diff --git a/lib/properties/borderStyle.js b/lib/properties/borderStyle.js index 37ea08da..67f46ecc 100644 --- a/lib/properties/borderStyle.js +++ b/lib/properties/borderStyle.js @@ -21,16 +21,16 @@ module.exports.parse = function parse(v) { module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - if (/^none$/i.test(v)) { - v = ""; - } if (parsers.hasVarFunc(v)) { this._setProperty("border", ""); this._setProperty("border-style", v); - return; + } else { + const positions = ["top", "right", "bottom", "left"]; + if (/^none$/i.test(v)) { + v = ""; + } + this._implicitSetter("border", "style", v, module.exports.parse, positions); } - const positions = ["top", "right", "bottom", "left"]; - this._implicitSetter("border", "style", v, module.exports.parse, positions); }, get() { return this.getPropertyValue("border-style"); diff --git a/lib/properties/borderTopColor.js b/lib/properties/borderTopColor.js index ce6a111a..4e8b6288 100644 --- a/lib/properties/borderTopColor.js +++ b/lib/properties/borderTopColor.js @@ -15,10 +15,12 @@ module.exports.definition = { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { this._setProperty("border", ""); - this._setProperty("border-top", ""); this._setProperty("border-color", ""); + this._setProperty("border-top", ""); + this._setProperty("border-top-color", v); + } else { + this._setProperty("border-top-color", module.exports.parse(v)); } - this._setProperty("border-top-color", module.exports.parse(v)); }, get() { return this.getPropertyValue("border-top-color"); diff --git a/lib/properties/borderTopStyle.js b/lib/properties/borderTopStyle.js index 49cfdefd..3f15e6f5 100644 --- a/lib/properties/borderTopStyle.js +++ b/lib/properties/borderTopStyle.js @@ -21,19 +21,21 @@ module.exports.parse = function parse(v) { module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - const val = module.exports.parse(v); - if (val === "none" || val === "hidden" || v === "") { - this._setProperty("border-top-style", ""); - this._setProperty("border-top-color", ""); - this._setProperty("border-top-width", ""); - return; - } if (parsers.hasVarFunc(v)) { this._setProperty("border", ""); this._setProperty("border-top", ""); this._setProperty("border-style", ""); + this._setProperty("border-top-style", v); + } else { + const val = module.exports.parse(v); + if (val === "" || val === "none" || val === "hidden") { + this._setProperty("border-top-style", ""); + this._setProperty("border-top-color", ""); + this._setProperty("border-top-width", ""); + } else { + this._setProperty("border-top-style", val); + } } - this._setProperty("border-top-style", val); }, get() { return this.getPropertyValue("border-top-style"); diff --git a/lib/properties/borderTopWidth.js b/lib/properties/borderTopWidth.js index c99f6983..7fda285c 100644 --- a/lib/properties/borderTopWidth.js +++ b/lib/properties/borderTopWidth.js @@ -16,10 +16,12 @@ module.exports.definition = { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { this._setProperty("border", ""); - this._setProperty("border-top", ""); this._setProperty("border-width", ""); + this._setProperty("border-top", ""); + this._setProperty("border-top-width", v); + } else { + this._setProperty("border-top-width", module.exports.parse(v)); } - this._setProperty("border-top-width", module.exports.parse(v)); }, get() { return this.getPropertyValue("border-top-width"); diff --git a/lib/properties/bottom.js b/lib/properties/bottom.js index bb4059a6..862a9507 100644 --- a/lib/properties/bottom.js +++ b/lib/properties/bottom.js @@ -13,7 +13,11 @@ module.exports.parse = function parse(v) { module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("bottom", module.exports.parse(v)); + if (parsers.hasVarFunc(v)) { + this._setProperty("bottom", v); + } else { + this._setProperty("bottom", module.exports.parse(v)); + } }, get() { return this.getPropertyValue("bottom"); diff --git a/lib/properties/clear.js b/lib/properties/clear.js index 23b835b9..77f4be03 100644 --- a/lib/properties/clear.js +++ b/lib/properties/clear.js @@ -23,7 +23,11 @@ module.exports.parse = function parse(v) { module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("clear", module.exports.parse(v)); + if (parsers.hasVarFunc(v)) { + this._setProperty("clear", v); + } else { + this._setProperty("clear", module.exports.parse(v)); + } }, get() { return this.getPropertyValue("clear"); diff --git a/lib/properties/clip.js b/lib/properties/clip.js index edf359fd..41c2a41b 100644 --- a/lib/properties/clip.js +++ b/lib/properties/clip.js @@ -37,7 +37,11 @@ module.exports.parse = function parse(v) { module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("clip", module.exports.parse(v)); + if (parsers.hasVarFunc(v)) { + this._setProperty("clip", v); + } else { + this._setProperty("clip", module.exports.parse(v)); + } }, get() { return this.getPropertyValue("clip"); diff --git a/lib/properties/color.js b/lib/properties/color.js index 86a7be74..9849d5d4 100644 --- a/lib/properties/color.js +++ b/lib/properties/color.js @@ -13,7 +13,11 @@ module.exports.parse = function parse(v) { module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("color", module.exports.parse(v)); + if (parsers.hasVarFunc(v)) { + this._setProperty("color", v); + } else { + this._setProperty("color", module.exports.parse(v)); + } }, get() { return this.getPropertyValue("color"); diff --git a/lib/properties/display.js b/lib/properties/display.js index 9e17ffa2..2d5ee51c 100644 --- a/lib/properties/display.js +++ b/lib/properties/display.js @@ -200,7 +200,11 @@ module.exports.parse = function parse(v) { module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("display", module.exports.parse(v)); + if (parsers.hasVarFunc(v)) { + this._setProperty("display", v); + } else { + this._setProperty("display", module.exports.parse(v)); + } }, get() { return this.getPropertyValue("display"); diff --git a/lib/properties/floodColor.js b/lib/properties/floodColor.js index 8f84bf91..e98834c8 100644 --- a/lib/properties/floodColor.js +++ b/lib/properties/floodColor.js @@ -13,7 +13,11 @@ module.exports.parse = function parse(v) { module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("flood-color", module.exports.parse(v)); + if (parsers.hasVarFunc(v)) { + this._setProperty("flood-color", v); + } else { + this._setProperty("flood-color", module.exports.parse(v)); + } }, get() { return this.getPropertyValue("flood-color"); diff --git a/lib/properties/height.js b/lib/properties/height.js index 40f4f4e6..0a2e11dd 100644 --- a/lib/properties/height.js +++ b/lib/properties/height.js @@ -14,7 +14,11 @@ module.exports.parse = function parse(v) { module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("height", module.exports.parse(v)); + if (parsers.hasVarFunc(v)) { + this._setProperty("height", v); + } else { + this._setProperty("height", module.exports.parse(v)); + } }, get() { return this.getPropertyValue("height"); diff --git a/lib/properties/left.js b/lib/properties/left.js index 531fb74e..c09b3c30 100644 --- a/lib/properties/left.js +++ b/lib/properties/left.js @@ -13,7 +13,11 @@ module.exports.parse = function parse(v) { module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("left", module.exports.parse(v)); + if (parsers.hasVarFunc(v)) { + this._setProperty("left", v); + } else { + this._setProperty("left", module.exports.parse(v)); + } }, get() { return this.getPropertyValue("left"); diff --git a/lib/properties/lightingColor.js b/lib/properties/lightingColor.js index 3357643e..153124e0 100644 --- a/lib/properties/lightingColor.js +++ b/lib/properties/lightingColor.js @@ -13,7 +13,11 @@ module.exports.parse = function parse(v) { module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("lighting-color", module.exports.parse(v)); + if (parsers.hasVarFunc(v)) { + this._setProperty("lighting-color", v); + } else { + this._setProperty("lighting-color", module.exports.parse(v)); + } }, get() { return this.getPropertyValue("lighting-color"); diff --git a/lib/properties/opacity.js b/lib/properties/opacity.js index 2479e7e6..23c454ed 100644 --- a/lib/properties/opacity.js +++ b/lib/properties/opacity.js @@ -29,7 +29,11 @@ module.exports.parse = function parse(v) { module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("opacity", module.exports.parse(v)); + if (parsers.hasVarFunc(v)) { + this._setProperty("opacity", v); + } else { + this._setProperty("opacity", module.exports.parse(v)); + } }, get() { return this.getPropertyValue("opacity"); diff --git a/lib/properties/outlineColor.js b/lib/properties/outlineColor.js index 0d240c5e..5677be66 100644 --- a/lib/properties/outlineColor.js +++ b/lib/properties/outlineColor.js @@ -13,7 +13,11 @@ module.exports.parse = function parse(v) { module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("outline-color", module.exports.parse(v)); + if (parsers.hasVarFunc(v)) { + this._setProperty("outline-color", v); + } else { + this._setProperty("outline-color", module.exports.parse(v)); + } }, get() { return this.getPropertyValue("outline-color"); diff --git a/lib/properties/right.js b/lib/properties/right.js index 06bfab10..408c84a8 100644 --- a/lib/properties/right.js +++ b/lib/properties/right.js @@ -13,7 +13,11 @@ module.exports.parse = function parse(v) { module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("right", module.exports.parse(v)); + if (parsers.hasVarFunc(v)) { + this._setProperty("right", v); + } else { + this._setProperty("right", module.exports.parse(v)); + } }, get() { return this.getPropertyValue("right"); diff --git a/lib/properties/stopColor.js b/lib/properties/stopColor.js index 5e781aea..3a88e1bb 100644 --- a/lib/properties/stopColor.js +++ b/lib/properties/stopColor.js @@ -13,7 +13,11 @@ module.exports.parse = function parse(v) { module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("stop-color", module.exports.parse(v)); + if (parsers.hasVarFunc(v)) { + this._setProperty("stop-color", v); + } else { + this._setProperty("stop-color", module.exports.parse(v)); + } }, get() { return this.getPropertyValue("stop-color"); diff --git a/lib/properties/top.js b/lib/properties/top.js index 051d3c2f..5cee254b 100644 --- a/lib/properties/top.js +++ b/lib/properties/top.js @@ -13,7 +13,11 @@ module.exports.parse = function parse(v) { module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("top", module.exports.parse(v)); + if (parsers.hasVarFunc(v)) { + this._setProperty("top", v); + } else { + this._setProperty("top", module.exports.parse(v)); + } }, get() { return this.getPropertyValue("top"); diff --git a/lib/properties/width.js b/lib/properties/width.js index 28c1fe0d..59e937f2 100644 --- a/lib/properties/width.js +++ b/lib/properties/width.js @@ -14,7 +14,11 @@ module.exports.parse = function parse(v) { module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("width", module.exports.parse(v)); + if (parsers.hasVarFunc(v)) { + this._setProperty("width", v); + } else { + this._setProperty("width", module.exports.parse(v)); + } }, get() { return this.getPropertyValue("width"); diff --git a/test/CSSStyleDeclaration.test.js b/test/CSSStyleDeclaration.test.js index eb0edee6..5a7e7ada 100644 --- a/test/CSSStyleDeclaration.test.js +++ b/test/CSSStyleDeclaration.test.js @@ -953,35 +953,35 @@ describe("CSSStyleDeclaration", () => { it("should not normalize if var() is included", () => { const style = new CSSStyleDeclaration(); - style.setProperty("width", "calc( /* comment */ 100% - calc(var(--foo) *2 ))"); + style.setProperty("line-height", "calc( /* comment */ 100% - calc(var(--foo) *2 ))"); assert.strictEqual( - style.getPropertyValue("width"), + style.getPropertyValue("line-height"), "calc( /* comment */ 100% - calc(var(--foo) *2 ))" ); }); it("supports abs", () => { const style = new CSSStyleDeclaration(); - style.setProperty("width", "abs(1 + 2 + 3)"); - assert.strictEqual(style.getPropertyValue("width"), "calc(6)"); + style.setProperty("line-height", "abs(1 - 2 * 3)"); + assert.strictEqual(style.getPropertyValue("line-height"), "calc(5)"); }); it("supports abs inside calc", () => { const style = new CSSStyleDeclaration(); - style.setProperty("width", "calc(abs(1) + abs(2))"); - assert.strictEqual(style.getPropertyValue("width"), "calc(3)"); + style.setProperty("line-height", "calc(abs(1) + abs(2))"); + assert.strictEqual(style.getPropertyValue("line-height"), "calc(3)"); }); it("supports sign", () => { const style = new CSSStyleDeclaration(); - style.setProperty("width", "sign(.1)"); - assert.strictEqual(style.getPropertyValue("width"), "calc(1)"); + style.setProperty("line-height", "sign(.1)"); + assert.strictEqual(style.getPropertyValue("line-height"), "calc(1)"); }); it("supports sign inside calc", () => { const style = new CSSStyleDeclaration(); - style.setProperty("width", "calc(sign(.1) + sign(.2))"); - assert.strictEqual(style.getPropertyValue("width"), "calc(2)"); + style.setProperty("line-height", "calc(sign(.1) + sign(.2))"); + assert.strictEqual(style.getPropertyValue("line-height"), "calc(2)"); }); it("no-op for setting undefined to width", () => { From 238f2b28848da9c72699fc35bf84fa4ade53efc2 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Fri, 1 Aug 2025 10:26:19 +0900 Subject: [PATCH 10/41] Export hasCalcFunc and add parseCalc --- lib/parsers.js | 24 ++++++++++++++----- test/parsers.test.js | 55 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 6 deletions(-) diff --git a/lib/parsers.js b/lib/parsers.js index 4c0b7193..bace4d14 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -86,6 +86,8 @@ const varRegEx = /^var\(/; const varContainedRegEx = /(?<=[*/\s(])var\(/; const calcRegEx = /^(?:a?(?:cos|sin|tan)|abs|atan2|calc|clamp|exp|hypot|log|max|min|mod|pow|rem|round|sign|sqrt)\(/; +const calcContainedRegEx = + /(?<=[*/\s(])(?:a?(?:cos|sin|tan)|abs|atan2|calc|clamp|exp|hypot|log|max|min|mod|pow|rem|round|sign|sqrt)\(/; const gradientRegEx = /^(?:repeating-)?(?:conic|linear|radial)-gradient\(/; const functionRegEx = /^([a-z][a-z\d]*(?:-[a-z\d]+)*)\(/i; @@ -144,15 +146,21 @@ exports.prepareValue = function prepareValue(value, globalObject = globalThis) { } }; -// Value starts with var() and/or contains var() +// Value starts with and/or contains CSS var() function exports.hasVarFunc = function hasVarFunc(val) { return varRegEx.test(val) || varContainedRegEx.test(val); }; +// Value starts with and/or contains CSS calc() related functions +exports.hasCalcFunc = function hasCalcFunc(val) { + return calcRegEx.test(val) || calcContainedRegEx.test(val); +}; + // Splits value into an array. // @see https://github.com/asamuzaK/cssColor/blob/main/src/js/util.ts exports.splitValue = splitValue; +// Parse CSS to AST or Object exports.parseCSS = function parseCSS(val, opt, toObject = false) { const ast = cssTree.parse(val, opt); if (toObject) { @@ -186,6 +194,10 @@ exports.isValidPropertyValue = function isValidPropertyValue(prop, val) { return error === null && matched !== null; }; +exports.parseCalc = function parseCalc(val, opt = { format: "specifiedValue" }) { + return cssCalc(val, opt); +}; + exports.parseNumber = function parseNumber(val, restrictToPositive = false) { if (val === "") { return ""; @@ -195,7 +207,7 @@ exports.parseNumber = function parseNumber(val, restrictToPositive = false) { case NUM_TYPE.VAR: return val; case NUM_TYPE.CALC: - return cssCalc(val, { + return exports.parseCalc(val, { format: "specifiedValue" }); case NUM_TYPE.NUMBER: { @@ -221,7 +233,7 @@ exports.parseLength = function parseLength(val, restrictToPositive = false) { case NUM_TYPE.VAR: return val; case NUM_TYPE.CALC: - return cssCalc(val, { + return exports.parseCalc(val, { format: "specifiedValue" }); case NUM_TYPE.NUMBER: @@ -253,7 +265,7 @@ exports.parsePercent = function parsePercent(val, restrictToPositive = false) { case NUM_TYPE.VAR: return val; case NUM_TYPE.CALC: - return cssCalc(val, { + return exports.parseCalc(val, { format: "specifiedValue" }); case NUM_TYPE.NUMBER: @@ -286,7 +298,7 @@ exports.parseMeasurement = function parseMeasurement(val, restrictToPositive = f case NUM_TYPE.VAR: return val; case NUM_TYPE.CALC: - return cssCalc(val, { + return exports.parseCalc(val, { format: "specifiedValue" }); case NUM_TYPE.NUMBER: @@ -319,7 +331,7 @@ exports.parseAngle = function parseAngle(val, normalizeDeg = false) { case NUM_TYPE.VAR: return val; case NUM_TYPE.CALC: - return cssCalc(val, { + return exports.parseCalc(val, { format: "specifiedValue" }); case NUM_TYPE.NUMBER: diff --git a/test/parsers.test.js b/test/parsers.test.js index 9f5b4635..d0e29079 100644 --- a/test/parsers.test.js +++ b/test/parsers.test.js @@ -148,6 +148,61 @@ describe("hasVarFunc", () => { }); }); +describe("hasCalcFunc", () => { + it("should return false", () => { + const input = ""; + const output = parsers.hasCalcFunc(input); + + assert.strictEqual(output, false); + }); + + it("should return false", () => { + const input = "1px"; + const output = parsers.hasCalcFunc(input); + + assert.strictEqual(output, false); + }); + + it("should return true", () => { + const input = "calc(1px * 2)"; + const output = parsers.hasCalcFunc(input); + + assert.strictEqual(output, true); + }); + + it("should return true", () => { + const input = "rgb(calc(255 / 3) 0 0)"; + const output = parsers.hasCalcFunc(input); + + assert.strictEqual(output, true); + }); +}); + +describe("parseNumber", () => { + it("should return empty string", () => { + const input = ""; + const output = parsers.parseCalc(input); + + assert.strictEqual(output, ""); + }); + + it('should return "calc(6px)"', () => { + const input = "calc(2px * 3)"; + const output = parsers.parseCalc(input, { + format: "specifiedValue" + }); + + assert.strictEqual(output, "calc(6px)"); + }); + + it('should return "rgb(85 0 0)"', () => { + const input = "rgb(calc(255 / 3) 0 0)"; + const output = parsers.parseCalc(input); + + assert.strictEqual(output, "rgb(85 0 0)"); + }); +}); + describe("parseNumber", () => { it("should return empty string", () => { const input = ""; From bc449078184e5b2b13d4902472808f9d65947ee6 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Fri, 1 Aug 2025 11:23:22 +0900 Subject: [PATCH 11/41] Update backgrounds --- lib/properties/background.js | 30 ++++++++++++++++---------- lib/properties/backgroundAttachment.js | 20 ++++------------- lib/properties/backgroundClip.js | 20 ++++------------- lib/properties/backgroundColor.js | 16 ++++++++++---- lib/properties/backgroundImage.js | 24 ++++++++++----------- lib/properties/backgroundOrigin.js | 20 ++++------------- lib/properties/backgroundPosition.js | 16 +++++++++----- lib/properties/backgroundRepeat.js | 12 ++++++++--- lib/properties/backgroundSize.js | 12 ++++++++--- 9 files changed, 83 insertions(+), 87 deletions(-) diff --git a/lib/properties/background.js b/lib/properties/background.js index a55c226c..35233600 100644 --- a/lib/properties/background.js +++ b/lib/properties/background.js @@ -10,17 +10,6 @@ const backgroundClip = require("./backgroundClip"); const backgroundAttachment = require("./backgroundAttachment"); const backgroundColor = require("./backgroundColor"); -module.exports.shorthandFor = new Map([ - ["background-image", backgroundImage], - ["background-position", backgroundPosition], - ["background-size", backgroundSize], - ["background-repeat", backgroundRepeat], - ["background-origin", backgroundOrigin], - ["background-clip", backgroundClip], - ["background-attachment", backgroundAttachment], - ["background-color", backgroundColor] -]); - const initialValues = new Map([ ["background-image", "none"], ["background-position", "0% 0%"], @@ -32,7 +21,26 @@ const initialValues = new Map([ ["background-color", "transparent"] ]); +module.exports.shorthandFor = new Map([ + ["background-image", backgroundImage], + ["background-position", backgroundPosition], + ["background-size", backgroundSize], + ["background-repeat", backgroundRepeat], + ["background-origin", backgroundOrigin], + ["background-clip", backgroundClip], + ["background-attachment", backgroundAttachment], + ["background-color", backgroundColor] +]); + module.exports.parse = function parse(v) { + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (!parsers.isValidPropertyValue("background", v)) { + return; + } const values = parsers.splitValue(v, { delimiter: "," }); diff --git a/lib/properties/backgroundAttachment.js b/lib/properties/backgroundAttachment.js index 7dbf977b..acfcd2a7 100644 --- a/lib/properties/backgroundAttachment.js +++ b/lib/properties/backgroundAttachment.js @@ -1,26 +1,14 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { if (v === "") { return v; - } - const values = parsers.splitValue(v, { - delimiter: "," - }); - const keywords = ["fixed", "scroll", "local"]; - const parsedValues = []; - for (const value of values) { - const parsedValue = parsers.parseKeyword(value, keywords); - if (parsedValue) { - parsedValues.push(parsedValue); - } else { - return; - } - } - if (parsedValues.length) { - return parsedValues.join(", "); + } else if (parsers.isValidPropertyValue("background-attachment", v)) { + const val = strings.asciiLowercase(v); + return parsers.splitValue(val, { delimiter: "," }).join(", "); } }; diff --git a/lib/properties/backgroundClip.js b/lib/properties/backgroundClip.js index e01948c9..bb132f2f 100644 --- a/lib/properties/backgroundClip.js +++ b/lib/properties/backgroundClip.js @@ -1,26 +1,14 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { if (v === "") { return v; - } - const values = parsers.splitValue(v, { - delimiter: "," - }); - const keywords = ["border-box", "padding-box", "content-box"]; - const parsedValues = []; - for (const value of values) { - const parsedValue = parsers.parseKeyword(value, keywords); - if (parsedValue) { - parsedValues.push(parsedValue); - } else { - return; - } - } - if (parsedValues.length) { - return parsedValues.join(", "); + } else if (parsers.isValidPropertyValue("background-clip", v)) { + const val = strings.asciiLowercase(v); + return parsers.splitValue(val, { delimiter: "," }).join(", "); } }; diff --git a/lib/properties/backgroundColor.js b/lib/properties/backgroundColor.js index 099ae13c..25a466da 100644 --- a/lib/properties/backgroundColor.js +++ b/lib/properties/backgroundColor.js @@ -1,13 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const val = parsers.parseColor(v); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("background-color", v)) { + const val = parsers.parseColor(v); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v); }; module.exports.definition = { diff --git a/lib/properties/backgroundImage.js b/lib/properties/backgroundImage.js index 7de2fa0f..9f440a17 100644 --- a/lib/properties/backgroundImage.js +++ b/lib/properties/backgroundImage.js @@ -1,25 +1,23 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { if (v === "") { return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); } - const values = parsers.splitValue(v, { - delimiter: "," - }); - const parsedValues = []; - for (const value of values) { - const parsedValue = parsers.parseImage(value); - if (parsedValue) { - parsedValues.push(parsedValue); - } else { - return; + if (parsers.isValidPropertyValue("background-image", v)) { + const val = parsers + .splitValue(v, { delimiter: "," }) + .map((value) => parsers.parseImage(value)) + .join(", "); + if (val) { + return val; } - } - if (parsedValues.length) { - return parsedValues.join(", "); + return strings.asciiLowercase(v); } }; diff --git a/lib/properties/backgroundOrigin.js b/lib/properties/backgroundOrigin.js index 7e12fb28..74af911d 100644 --- a/lib/properties/backgroundOrigin.js +++ b/lib/properties/backgroundOrigin.js @@ -1,26 +1,14 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { if (v === "") { return v; - } - const values = parsers.splitValue(v, { - delimiter: "," - }); - const keywords = ["border-box", "padding-box", "content-box"]; - const parsedValues = []; - for (const value of values) { - const parsedValue = parsers.parseKeyword(value, keywords); - if (parsedValue) { - parsedValues.push(parsedValue); - } else { - return; - } - } - if (parsedValues.length) { - return parsedValues.join(", "); + } else if (parsers.isValidPropertyValue("background-origin", v)) { + const val = strings.asciiLowercase(v); + return parsers.splitValue(val, { delimiter: "," }).join(", "); } }; diff --git a/lib/properties/backgroundPosition.js b/lib/properties/backgroundPosition.js index c91948c5..33579e8f 100644 --- a/lib/properties/backgroundPosition.js +++ b/lib/properties/backgroundPosition.js @@ -2,18 +2,24 @@ const parsers = require("../parsers"); +const keyX = ["left", "right"]; +const keyY = ["top", "bottom"]; +const keywordsX = ["center", ...keyX]; +const keywordsY = ["center", ...keyY]; +const keywords = ["center", ...keyX, ...keyY]; + module.exports.parse = function parse(v) { if (v === "") { return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (!parsers.isValidPropertyValue("background-position", v)) { + return; } const values = parsers.splitValue(v, { delimiter: "," }); - const keyX = ["left", "right"]; - const keyY = ["top", "bottom"]; - const keywordsX = ["center", ...keyX]; - const keywordsY = ["center", ...keyY]; - const keywords = ["center", ...keyX, ...keyY]; const parsedValues = []; for (const value of values) { const parts = parsers.splitValue(value); diff --git a/lib/properties/backgroundRepeat.js b/lib/properties/backgroundRepeat.js index fa173bd4..ead57829 100644 --- a/lib/properties/backgroundRepeat.js +++ b/lib/properties/backgroundRepeat.js @@ -2,16 +2,22 @@ const parsers = require("../parsers"); +const keywordsAxis = ["repeat-x", "repeat-y"]; +const keywordsRepeat = ["repeat", "no-repeat", "space", "round"]; +const keywords = [...keywordsAxis, ...keywordsRepeat]; + module.exports.parse = function parse(v) { if (v === "") { return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (!parsers.isValidPropertyValue("background-repeat", v)) { + return; } const values = parsers.splitValue(v, { delimiter: "," }); - const keywordsAxis = ["repeat-x", "repeat-y"]; - const keywordsRepeat = ["repeat", "no-repeat", "space", "round"]; - const keywords = [...keywordsAxis, ...keywordsRepeat]; const parsedValues = []; for (const value of values) { const parts = parsers.splitValue(value); diff --git a/lib/properties/backgroundSize.js b/lib/properties/backgroundSize.js index b1134327..269ef301 100644 --- a/lib/properties/backgroundSize.js +++ b/lib/properties/backgroundSize.js @@ -2,16 +2,22 @@ const parsers = require("../parsers"); +const keywordsRatio = ["contain", "cover"]; +const keywordsRepeat = ["auto"]; +const keywords = [...keywordsRatio, ...keywordsRepeat]; + module.exports.parse = function parse(v) { if (v === "") { return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (!parsers.isValidPropertyValue("background-size", v)) { + return; } const values = parsers.splitValue(v, { delimiter: "," }); - const keywordsRatio = ["contain", "cover"]; - const keywordsRepeat = ["auto"]; - const keywords = [...keywordsRatio, ...keywordsRepeat]; const parsedValues = []; for (const value of values) { const parts = parsers.splitValue(value); From c12fadc3bcf5bbe15209309b236aa36047b102b4 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Fri, 1 Aug 2025 11:58:44 +0900 Subject: [PATCH 12/41] Update borders --- lib/properties/borderBottomColor.js | 16 ++++++++++++---- lib/properties/borderBottomStyle.js | 19 ++++++------------- lib/properties/borderBottomWidth.js | 17 ++++++++++++----- lib/properties/borderCollapse.js | 7 ++++++- lib/properties/borderColor.js | 16 ++++++++++++---- lib/properties/borderLeftColor.js | 16 ++++++++++++---- lib/properties/borderLeftStyle.js | 19 ++++++------------- lib/properties/borderLeftWidth.js | 17 ++++++++++++----- lib/properties/borderRightColor.js | 16 ++++++++++++---- lib/properties/borderRightStyle.js | 19 ++++++------------- lib/properties/borderRightWidth.js | 17 ++++++++++++----- lib/properties/borderSpacing.js | 5 +++++ lib/properties/borderStyle.js | 19 ++++++------------- lib/properties/borderTopColor.js | 16 ++++++++++++---- lib/properties/borderTopStyle.js | 19 ++++++------------- lib/properties/borderTopWidth.js | 17 ++++++++++++----- lib/properties/borderWidth.js | 17 ++++++++++++----- 17 files changed, 161 insertions(+), 111 deletions(-) diff --git a/lib/properties/borderBottomColor.js b/lib/properties/borderBottomColor.js index 686a5cae..aab19722 100644 --- a/lib/properties/borderBottomColor.js +++ b/lib/properties/borderBottomColor.js @@ -1,13 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const val = parsers.parseColor(v); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("border-bottom-color", v)) { + const val = parsers.parseColor(v); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v); }; module.exports.definition = { diff --git a/lib/properties/borderBottomStyle.js b/lib/properties/borderBottomStyle.js index 03f19174..029b9797 100644 --- a/lib/properties/borderBottomStyle.js +++ b/lib/properties/borderBottomStyle.js @@ -1,21 +1,14 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const keywords = [ - "none", - "hidden", - "dotted", - "dashed", - "solid", - "double", - "groove", - "ridge", - "inset", - "outset" - ]; - return parsers.parseKeyword(v, keywords); + if (v === "") { + return v; + } else if (parsers.isValidPropertyValue("border-bottom-style", v)) { + return strings.asciiLowercase(v); + } }; module.exports.definition = { diff --git a/lib/properties/borderBottomWidth.js b/lib/properties/borderBottomWidth.js index 0bea5f63..741ab211 100644 --- a/lib/properties/borderBottomWidth.js +++ b/lib/properties/borderBottomWidth.js @@ -1,14 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const keywords = ["thin", "medium", "thick"]; - const key = parsers.parseKeyword(v, keywords); - if (key) { - return key; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("border-bottom-width", v)) { + const val = parsers.parseLength(v, true); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseLength(v, true); }; module.exports.definition = { diff --git a/lib/properties/borderCollapse.js b/lib/properties/borderCollapse.js index 1aed0f64..2c92550b 100644 --- a/lib/properties/borderCollapse.js +++ b/lib/properties/borderCollapse.js @@ -1,9 +1,14 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - return parsers.parseKeyword(v, ["collapse", "separate"]); + if (v === "") { + return v; + } else if (parsers.isValidPropertyValue("border-collapse", v)) { + return strings.asciiLowercase(v); + } }; module.exports.definition = { diff --git a/lib/properties/borderColor.js b/lib/properties/borderColor.js index 8725ff84..3892dd66 100644 --- a/lib/properties/borderColor.js +++ b/lib/properties/borderColor.js @@ -1,13 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const val = parsers.parseColor(v); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("border-color", v)) { + const val = parsers.parseColor(v); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v); }; module.exports.definition = { diff --git a/lib/properties/borderLeftColor.js b/lib/properties/borderLeftColor.js index 7816ab77..f5183936 100644 --- a/lib/properties/borderLeftColor.js +++ b/lib/properties/borderLeftColor.js @@ -1,13 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const val = parsers.parseColor(v); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("border-left-color", v)) { + const val = parsers.parseColor(v); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v); }; module.exports.definition = { diff --git a/lib/properties/borderLeftStyle.js b/lib/properties/borderLeftStyle.js index e82aa7a6..8aa6bdb6 100644 --- a/lib/properties/borderLeftStyle.js +++ b/lib/properties/borderLeftStyle.js @@ -1,21 +1,14 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const keywords = [ - "none", - "hidden", - "dotted", - "dashed", - "solid", - "double", - "groove", - "ridge", - "inset", - "outset" - ]; - return parsers.parseKeyword(v, keywords); + if (v === "") { + return v; + } else if (parsers.isValidPropertyValue("border-left-style", v)) { + return strings.asciiLowercase(v); + } }; module.exports.definition = { diff --git a/lib/properties/borderLeftWidth.js b/lib/properties/borderLeftWidth.js index 3038c16b..189bf62f 100644 --- a/lib/properties/borderLeftWidth.js +++ b/lib/properties/borderLeftWidth.js @@ -1,14 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const keywords = ["thin", "medium", "thick"]; - const key = parsers.parseKeyword(v, keywords); - if (key) { - return key; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("border-left-width", v)) { + const val = parsers.parseLength(v, true); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseLength(v, true); }; module.exports.definition = { diff --git a/lib/properties/borderRightColor.js b/lib/properties/borderRightColor.js index 61c55802..f69b7c1a 100644 --- a/lib/properties/borderRightColor.js +++ b/lib/properties/borderRightColor.js @@ -1,13 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const val = parsers.parseColor(v); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("border-right-color", v)) { + const val = parsers.parseColor(v); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v); }; module.exports.definition = { diff --git a/lib/properties/borderRightStyle.js b/lib/properties/borderRightStyle.js index 6caba8cc..c8bed47a 100644 --- a/lib/properties/borderRightStyle.js +++ b/lib/properties/borderRightStyle.js @@ -1,21 +1,14 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const keywords = [ - "none", - "hidden", - "dotted", - "dashed", - "solid", - "double", - "groove", - "ridge", - "inset", - "outset" - ]; - return parsers.parseKeyword(v, keywords); + if (v === "") { + return v; + } else if (parsers.isValidPropertyValue("border-right-style", v)) { + return strings.asciiLowercase(v); + } }; module.exports.definition = { diff --git a/lib/properties/borderRightWidth.js b/lib/properties/borderRightWidth.js index 6a08e27f..b035376c 100644 --- a/lib/properties/borderRightWidth.js +++ b/lib/properties/borderRightWidth.js @@ -1,14 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const keywords = ["thin", "medium", "thick"]; - const key = parsers.parseKeyword(v, keywords); - if (key) { - return key; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("border-right-width", v)) { + const val = parsers.parseLength(v, true); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseLength(v, true); }; module.exports.definition = { diff --git a/lib/properties/borderSpacing.js b/lib/properties/borderSpacing.js index 3ff5a96e..4b282c5b 100644 --- a/lib/properties/borderSpacing.js +++ b/lib/properties/borderSpacing.js @@ -5,6 +5,11 @@ const parsers = require("../parsers"); module.exports.parse = function parse(v) { if (v === "") { return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (!parsers.isValidPropertyValue("border-spacing", v)) { + return; } const key = parsers.parseKeyword(v); if (key) { diff --git a/lib/properties/borderStyle.js b/lib/properties/borderStyle.js index 67f46ecc..2c7cd008 100644 --- a/lib/properties/borderStyle.js +++ b/lib/properties/borderStyle.js @@ -1,21 +1,14 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const keywords = [ - "none", - "hidden", - "dotted", - "dashed", - "solid", - "double", - "groove", - "ridge", - "inset", - "outset" - ]; - return parsers.parseKeyword(v, keywords); + if (v === "") { + return v; + } else if (parsers.isValidPropertyValue("border-style", v)) { + return strings.asciiLowercase(v); + } }; module.exports.definition = { diff --git a/lib/properties/borderTopColor.js b/lib/properties/borderTopColor.js index 4e8b6288..6e9eda98 100644 --- a/lib/properties/borderTopColor.js +++ b/lib/properties/borderTopColor.js @@ -1,13 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const val = parsers.parseColor(v); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("border-top-color", v)) { + const val = parsers.parseColor(v); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v); }; module.exports.definition = { diff --git a/lib/properties/borderTopStyle.js b/lib/properties/borderTopStyle.js index 3f15e6f5..4bbdeff7 100644 --- a/lib/properties/borderTopStyle.js +++ b/lib/properties/borderTopStyle.js @@ -1,21 +1,14 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const keywords = [ - "none", - "hidden", - "dotted", - "dashed", - "solid", - "double", - "groove", - "ridge", - "inset", - "outset" - ]; - return parsers.parseKeyword(v, keywords); + if (v === "") { + return v; + } else if (parsers.isValidPropertyValue("border-top-style", v)) { + return strings.asciiLowercase(v); + } }; module.exports.definition = { diff --git a/lib/properties/borderTopWidth.js b/lib/properties/borderTopWidth.js index 7fda285c..709ae86d 100644 --- a/lib/properties/borderTopWidth.js +++ b/lib/properties/borderTopWidth.js @@ -1,14 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const keywords = ["thin", "medium", "thick"]; - const key = parsers.parseKeyword(v, keywords); - if (key) { - return key; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("border-top-width", v)) { + const val = parsers.parseLength(v, true); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseLength(v, true); }; module.exports.definition = { diff --git a/lib/properties/borderWidth.js b/lib/properties/borderWidth.js index cf4ea261..6a492055 100644 --- a/lib/properties/borderWidth.js +++ b/lib/properties/borderWidth.js @@ -1,14 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const keywords = ["thin", "medium", "thick"]; - const key = parsers.parseKeyword(v, keywords); - if (key) { - return key; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("border-width", v)) { + const val = parsers.parseLength(v, true); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseLength(v, true); }; module.exports.definition = { From 0416d4f9748fe50a2bfd21c5c14c1d8c8d32636f Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Sat, 2 Aug 2025 05:38:21 +0900 Subject: [PATCH 13/41] Update flex --- lib/properties/flex.js | 39 ++++++++++++++++++++++++++---------- lib/properties/flexBasis.js | 17 +++++++++++----- lib/properties/flexGrow.js | 9 ++++++++- lib/properties/flexShrink.js | 9 ++++++++- test/properties.test.js | 4 ++-- 5 files changed, 58 insertions(+), 20 deletions(-) diff --git a/lib/properties/flex.js b/lib/properties/flex.js index aa94ba0a..6ed07569 100644 --- a/lib/properties/flex.js +++ b/lib/properties/flex.js @@ -5,6 +5,8 @@ const flexGrow = require("./flexGrow"); const flexShrink = require("./flexShrink"); const flexBasis = require("./flexBasis"); +const replaceKeys = new Map([["initial", "0 1 auto"]]); + module.exports.shorthandFor = new Map([ ["flex-grow", flexGrow], ["flex-shrink", flexShrink], @@ -12,18 +14,27 @@ module.exports.shorthandFor = new Map([ ]); module.exports.parse = function parse(v) { + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (!parsers.isValidPropertyValue("flex", v)) { + return; + } const key = parsers.parseKeyword(v, ["auto", "none"]); if (key) { - if (key === "auto") { - return "1 1 auto"; + switch (key) { + case "auto": { + return "1 1 auto"; + } + case "none": { + return "0 0 auto"; + } + default: { + return key; + } } - if (key === "none") { - return "0 0 auto"; - } - if (key === "initial") { - return "0 1 auto"; - } - return; } const obj = parsers.parseShorthand(v, module.exports.shorthandFor); if (obj) { @@ -47,12 +58,18 @@ module.exports.definition = { this._shorthandSetter("flex", "", module.exports.shorthandFor); this._setProperty("flex", v); } else { - this._shorthandSetter("flex", module.exports.parse(v), module.exports.shorthandFor); + const val = module.exports.parse(v); + if (replaceKeys.has(val)) { + this._shorthandSetter("flex", replaceKeys.get(val), module.exports.shorthandFor); + this._setProperty("flex", val); + } else { + this._shorthandSetter("flex", val, module.exports.shorthandFor); + } } }, get() { let val = this.getPropertyValue("flex"); - if (parsers.hasVarFunc(val)) { + if (parsers.hasVarFunc(val) || replaceKeys.has(val)) { return val; } val = this._shorthandGetter("flex", module.exports.shorthandFor); diff --git a/lib/properties/flexBasis.js b/lib/properties/flexBasis.js index 1c9d99ec..3b4745a0 100644 --- a/lib/properties/flexBasis.js +++ b/lib/properties/flexBasis.js @@ -1,14 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const val = parsers.parseMeasurement(v); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("flex-basis", v)) { + const val = parsers.parseMeasurement(v); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - const keywords = ["content", "auto", "min-content", "max-content"]; - return parsers.parseKeyword(v, keywords); }; module.exports.definition = { diff --git a/lib/properties/flexGrow.js b/lib/properties/flexGrow.js index e1be6754..46d6ae6e 100644 --- a/lib/properties/flexGrow.js +++ b/lib/properties/flexGrow.js @@ -3,7 +3,14 @@ const parsers = require("../parsers"); module.exports.parse = function parse(v) { - return parsers.parseNumber(v, true); + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("flex-grow", v)) { + return parsers.parseNumber(v, true); + } }; module.exports.definition = { diff --git a/lib/properties/flexShrink.js b/lib/properties/flexShrink.js index c9d9dab7..4ae31ca3 100644 --- a/lib/properties/flexShrink.js +++ b/lib/properties/flexShrink.js @@ -3,7 +3,14 @@ const parsers = require("../parsers"); module.exports.parse = function parse(v) { - return parsers.parseNumber(v, true); + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("flex-shrink", v)) { + return parsers.parseNumber(v, true); + } }; module.exports.definition = { diff --git a/test/properties.test.js b/test/properties.test.js index f322b040..8023655d 100644 --- a/test/properties.test.js +++ b/test/properties.test.js @@ -1584,11 +1584,11 @@ describe("flex box", () => { }); it("flex should set / get keyword", () => { - testPropertyValue("flex", "initial", "0 1 auto"); + testPropertyValue("flex", "auto", "1 1 auto"); }); it("flex should set / get keyword", () => { - testPropertyValue("flex", "auto", "1 1 auto"); + testPropertyValue("flex", "initial", "initial"); }); it("flex shorthand should set / get longhand value", () => { From 0a4f0fb173a2b3b23d3c3473fd65e0c2423a72bf Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Sat, 2 Aug 2025 08:55:44 +0900 Subject: [PATCH 14/41] Update numeric value parsers --- lib/parsers.js | 175 +++++++++++++++++++++---------------------- test/parsers.test.js | 6 +- 2 files changed, 89 insertions(+), 92 deletions(-) diff --git a/lib/parsers.js b/lib/parsers.js index bace4d14..22ec845d 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -19,12 +19,10 @@ const GLOBAL_VALUE = Object.freeze(["initial", "inherit", "unset", "revert", "re // Numeric data types const NUM_TYPE = Object.freeze({ UNDEFINED: 0, - VAR: 1, - NUMBER: 2, - PERCENT: 4, - LENGTH: 8, - ANGLE: 0x10, - CALC: 0x20 + NUMBER: 1, + PERCENT: 2, + LENGTH: 4, + ANGLE: 8 }); // System colors @@ -78,26 +76,21 @@ const SYS_COLOR = Object.freeze([ // Regular expressions const DIGIT = "(?:0|[1-9]\\d*)"; const NUMBER = `[+-]?(?:${DIGIT}(?:\\.\\d*)?|\\.\\d+)(?:e-?${DIGIT})?`; +const CALC_KEY = + "(?:a?(?:cos|sin|tan)|abs|atan2|calc|clamp|exp|hypot|log|max|min|mod|pow|rem|round|sign|sqrt)"; const unitRegEx = new RegExp(`^(${NUMBER})([a-z]+|%)?$`, "i"); const urlRegEx = /^url\(\s*((?:[^)]|\\\))*)\s*\)$/; const keywordRegEx = /^[a-z]+(?:-[a-z]+)*$/i; const stringRegEx = /^("[^"]*"|'[^']*')$/; const varRegEx = /^var\(/; const varContainedRegEx = /(?<=[*/\s(])var\(/; -const calcRegEx = - /^(?:a?(?:cos|sin|tan)|abs|atan2|calc|clamp|exp|hypot|log|max|min|mod|pow|rem|round|sign|sqrt)\(/; -const calcContainedRegEx = - /(?<=[*/\s(])(?:a?(?:cos|sin|tan)|abs|atan2|calc|clamp|exp|hypot|log|max|min|mod|pow|rem|round|sign|sqrt)\(/; +const calcRegEx = new RegExp(`^${CALC_KEY}\\(`); +const calcNameRegEx = new RegExp(`^${CALC_KEY}$`); +const calcContainedRegEx = new RegExp(`(?<=[*/\\s(])${CALC_KEY}\\(`); const gradientRegEx = /^(?:repeating-)?(?:conic|linear|radial)-gradient\(/; const functionRegEx = /^([a-z][a-z\d]*(?:-[a-z\d]+)*)\(/i; const getNumericType = function getNumericType(val) { - if (varRegEx.test(val)) { - return NUM_TYPE.VAR; - } - if (calcRegEx.test(val)) { - return NUM_TYPE.CALC; - } if (unitRegEx.test(val)) { const [, , unit] = unitRegEx.exec(val); if (!unit) { @@ -195,52 +188,68 @@ exports.isValidPropertyValue = function isValidPropertyValue(prop, val) { }; exports.parseCalc = function parseCalc(val, opt = { format: "specifiedValue" }) { - return cssCalc(val, opt); + if (val === "" || exports.hasVarFunc(val) || !exports.hasCalcFunc(val)) { + return val; + } + const obj = exports.parseCSS(val, { context: "value" }, true); + if (!obj?.children) { + return; + } + const { children: items } = obj; + const values = []; + for (const item of items) { + const { type: itemType, name: itemName, value: itemValue } = item; + if (itemType === "Function") { + const value = cssTree.generate(item).replaceAll(")", ") ").trim(); + if (calcNameRegEx.test(itemName)) { + const newValue = cssCalc(value, opt); + values.push(newValue); + } else { + values.push(value); + } + } else if (itemType === "String") { + values.push(`"${itemValue}"`); + } else { + values.push(itemName ?? itemValue); + } + } + return values.join(" "); }; exports.parseNumber = function parseNumber(val, restrictToPositive = false) { - if (val === "") { - return ""; + if (val === "" || exports.hasVarFunc(val)) { + return val; + } else if (exports.hasCalcFunc(val)) { + return exports.parseCalc(val, { + format: "specifiedValue" + }); } const type = getNumericType(val); - switch (type) { - case NUM_TYPE.VAR: - return val; - case NUM_TYPE.CALC: - return exports.parseCalc(val, { - format: "specifiedValue" - }); - case NUM_TYPE.NUMBER: { - const num = parseFloat(val); - if (restrictToPositive && num < 0) { - return; - } - return `${num}`; + if (type === NUM_TYPE.NUMBER) { + const num = parseFloat(val); + if (restrictToPositive && num < 0) { + return; } - default: - if (varContainedRegEx.test(val)) { - return val; - } + return `${num}`; } }; exports.parseLength = function parseLength(val, restrictToPositive = false) { - if (val === "") { - return ""; + if (val === "" || exports.hasVarFunc(val)) { + return val; + } else if (exports.hasCalcFunc(val)) { + return exports.parseCalc(val, { + format: "specifiedValue" + }); } const type = getNumericType(val); switch (type) { - case NUM_TYPE.VAR: - return val; - case NUM_TYPE.CALC: - return exports.parseCalc(val, { - format: "specifiedValue" - }); - case NUM_TYPE.NUMBER: + case NUM_TYPE.NUMBER: { if (parseFloat(val) === 0) { return "0px"; } - return; + break; + } case NUM_TYPE.LENGTH: { const [, numVal, unit] = unitRegEx.exec(val); const num = parseFloat(numVal); @@ -250,29 +259,25 @@ exports.parseLength = function parseLength(val, restrictToPositive = false) { return `${num}${asciiLowercase(unit)}`; } default: - if (varContainedRegEx.test(val)) { - return val; - } } }; exports.parsePercent = function parsePercent(val, restrictToPositive = false) { - if (val === "") { - return ""; + if (val === "" || exports.hasVarFunc(val)) { + return val; + } else if (exports.hasCalcFunc(val)) { + return exports.parseCalc(val, { + format: "specifiedValue" + }); } const type = getNumericType(val); switch (type) { - case NUM_TYPE.VAR: - return val; - case NUM_TYPE.CALC: - return exports.parseCalc(val, { - format: "specifiedValue" - }); - case NUM_TYPE.NUMBER: + case NUM_TYPE.NUMBER: { if (parseFloat(val) === 0) { return "0%"; } - return; + break; + } case NUM_TYPE.PERCENT: { const [, numVal, unit] = unitRegEx.exec(val); const num = parseFloat(numVal); @@ -282,30 +287,26 @@ exports.parsePercent = function parsePercent(val, restrictToPositive = false) { return `${num}${asciiLowercase(unit)}`; } default: - if (varContainedRegEx.test(val)) { - return val; - } } }; // Either a length or a percent. exports.parseMeasurement = function parseMeasurement(val, restrictToPositive = false) { - if (val === "") { - return ""; + if (val === "" || exports.hasVarFunc(val)) { + return val; + } else if (exports.hasCalcFunc(val)) { + return exports.parseCalc(val, { + format: "specifiedValue" + }); } const type = getNumericType(val); switch (type) { - case NUM_TYPE.VAR: - return val; - case NUM_TYPE.CALC: - return exports.parseCalc(val, { - format: "specifiedValue" - }); - case NUM_TYPE.NUMBER: + case NUM_TYPE.NUMBER: { if (parseFloat(val) === 0) { return "0px"; } - return; + break; + } case NUM_TYPE.LENGTH: case NUM_TYPE.PERCENT: { const [, numVal, unit] = unitRegEx.exec(val); @@ -316,29 +317,25 @@ exports.parseMeasurement = function parseMeasurement(val, restrictToPositive = f return `${num}${asciiLowercase(unit)}`; } default: - if (varContainedRegEx.test(val)) { - return val; - } } }; exports.parseAngle = function parseAngle(val, normalizeDeg = false) { - if (val === "") { - return ""; + if (val === "" || exports.hasVarFunc(val)) { + return val; + } else if (exports.hasCalcFunc(val)) { + return exports.parseCalc(val, { + format: "specifiedValue" + }); } const type = getNumericType(val); switch (type) { - case NUM_TYPE.VAR: - return val; - case NUM_TYPE.CALC: - return exports.parseCalc(val, { - format: "specifiedValue" - }); - case NUM_TYPE.NUMBER: + case NUM_TYPE.NUMBER: { if (parseFloat(val) === 0) { return "0deg"; } - return; + break; + } case NUM_TYPE.ANGLE: { let [, numVal, unit] = unitRegEx.exec(val); numVal = parseFloat(numVal); @@ -354,9 +351,6 @@ exports.parseAngle = function parseAngle(val, normalizeDeg = false) { return `${numVal}${unit}`; } default: - if (varContainedRegEx.test(val)) { - return val; - } } }; @@ -544,7 +538,10 @@ exports.parseShorthand = function parseShorthand(val, shorthandFor, preserve = f } const parts = splitValue(val); const shorthandArr = [...shorthandFor]; - for (const part of parts) { + for (let part of parts) { + if (exports.hasCalcFunc(part)) { + part = exports.parseCalc(part); + } let partValid = false; for (let i = 0; i < shorthandArr.length; i++) { const [property, value] = shorthandArr[i]; diff --git a/test/parsers.test.js b/test/parsers.test.js index d0e29079..a7a8d9a2 100644 --- a/test/parsers.test.js +++ b/test/parsers.test.js @@ -178,7 +178,7 @@ describe("hasCalcFunc", () => { }); }); -describe("parseNumber", () => { +describe("parseCalc", () => { it("should return empty string", () => { const input = ""; const output = parsers.parseCalc(input); @@ -195,11 +195,11 @@ describe("parseNumber", () => { assert.strictEqual(output, "calc(6px)"); }); - it('should return "rgb(85 0 0)"', () => { + it('should return "rgb(calc(255/3) 0 0)"', () => { const input = "rgb(calc(255 / 3) 0 0)"; const output = parsers.parseCalc(input); - assert.strictEqual(output, "rgb(85 0 0)"); + assert.strictEqual(output, "rgb(calc(255/3) 0 0)"); }); }); From c696fa817757fb9416047ad660d1fbdd84c731a8 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Sat, 2 Aug 2025 08:57:04 +0900 Subject: [PATCH 15/41] Update fonts --- lib/properties/font.js | 10 +++++++++- lib/properties/fontFamily.js | 32 ++++++++++++++++++-------------- lib/properties/fontSize.js | 28 ++++++++++++---------------- lib/properties/fontStyle.js | 8 ++++++-- lib/properties/fontVariant.js | 12 +++++++----- lib/properties/fontWeight.js | 20 +++++++++++++++----- lib/properties/lineHeight.js | 18 +++++++++++------- 7 files changed, 78 insertions(+), 50 deletions(-) diff --git a/lib/properties/font.js b/lib/properties/font.js index 8002f0b2..ef6986f5 100644 --- a/lib/properties/font.js +++ b/lib/properties/font.js @@ -8,6 +8,8 @@ const fontSize = require("./fontSize"); const lineHeight = require("./lineHeight"); const fontFamily = require("./fontFamily"); +const keywords = ["caption", "icon", "menu", "message-box", "small-caption", "status-bar"]; + module.exports.shorthandFor = new Map([ ["font-style", fontStyle], ["font-variant", fontVariant], @@ -18,7 +20,13 @@ module.exports.shorthandFor = new Map([ ]); module.exports.parse = function parse(v) { - const keywords = ["caption", "icon", "menu", "message-box", "small-caption", "status-bar"]; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } else if (!parsers.isValidPropertyValue("font", v)) { + return; + } const key = parsers.parseKeyword(v, keywords); if (key) { return key; diff --git a/lib/properties/fontFamily.js b/lib/properties/fontFamily.js index 7df55a8b..b3e9359c 100644 --- a/lib/properties/fontFamily.js +++ b/lib/properties/fontFamily.js @@ -2,24 +2,28 @@ const parsers = require("../parsers"); +const keywords = [ + "serif", + "sans-serif", + "cursive", + "fantasy", + "monospace", + "system-ui", + "math", + "ui-serif", + "ui-sans-serif", + "ui-monospace", + "ui-rounded" +]; +const genericValues = ["fangsong", "kai", "khmer-mul", "nastaliq"]; + module.exports.parse = function parse(v) { if (v === "") { return v; } - const keywords = [ - "serif", - "sans-serif", - "cursive", - "fantasy", - "monospace", - "system-ui", - "math", - "ui-serif", - "ui-sans-serif", - "ui-monospace", - "ui-rounded" - ]; - const genericValues = ["fangsong", "kai", "khmer-mul", "nastaliq"]; + if (!parsers.isValidPropertyValue("font-family", v)) { + return; + } const val = parsers.splitValue(v, { delimiter: "," }); diff --git a/lib/properties/fontSize.js b/lib/properties/fontSize.js index 482990eb..acbcb021 100644 --- a/lib/properties/fontSize.js +++ b/lib/properties/fontSize.js @@ -1,25 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const val = parsers.parseMeasurement(v, true); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("font-size", v)) { + const val = parsers.parseMeasurement(v, true); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - const keywords = [ - "xx-small", - "x-small", - "small", - "medium", - "large", - "x-large", - "xx-large", - "xxx-large", - "smaller", - "larger" - ]; - return parsers.parseKeyword(v, keywords); }; module.exports.definition = { diff --git a/lib/properties/fontStyle.js b/lib/properties/fontStyle.js index be72332c..5f45ddb2 100644 --- a/lib/properties/fontStyle.js +++ b/lib/properties/fontStyle.js @@ -1,10 +1,14 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const keywords = ["normal", "italic", "oblique"]; - return parsers.parseKeyword(v, keywords); + if (v === "") { + return v; + } else if (parsers.isValidPropertyValue("font-style", v)) { + return strings.asciiLowercase(v); + } }; module.exports.definition = { diff --git a/lib/properties/fontVariant.js b/lib/properties/fontVariant.js index 39b0b057..4a12c376 100644 --- a/lib/properties/fontVariant.js +++ b/lib/properties/fontVariant.js @@ -1,14 +1,16 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const num = parsers.parseNumber(v, true); - if (num && parseFloat(num) <= 1000) { - return num; + if (v === "") { + return v; + } + if (parsers.isValidPropertyValue("font-variant", v)) { + const val = strings.asciiLowercase(v); + return parsers.splitValue(val).join(" "); } - const keywords = ["normal", "none", "small-caps"]; - return parsers.parseKeyword(v, keywords); }; module.exports.definition = { diff --git a/lib/properties/fontWeight.js b/lib/properties/fontWeight.js index 29f90502..aaead851 100644 --- a/lib/properties/fontWeight.js +++ b/lib/properties/fontWeight.js @@ -1,14 +1,24 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const num = parsers.parseNumber(v, true); - if (num && parseFloat(num) <= 1000) { - return num; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("font-weight", v)) { + const num = parsers.parseNumber(v, true); + if (num) { + if (num < 1 || parseFloat(num) > 1000) { + return; + } + return num; + } + return strings.asciiLowercase(v); } - const keywords = ["normal", "bold", "lighter", "bolder"]; - return parsers.parseKeyword(v, keywords); }; module.exports.definition = { diff --git a/lib/properties/lineHeight.js b/lib/properties/lineHeight.js index c56aa9e9..bff040a8 100644 --- a/lib/properties/lineHeight.js +++ b/lib/properties/lineHeight.js @@ -1,17 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const val = parsers.parseKeyword(v, ["normal"]); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); } - const num = parsers.parseNumber(v, true); - if (num) { - return num; + if (parsers.isValidPropertyValue("line-height", v)) { + const val = parsers.parseNumber(v, true) ?? parsers.parseMeasurement(v, true); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseMeasurement(v, true); }; module.exports.definition = { From 73e0588ad73bfe8a4225e4a295fc3c490ba7d7fc Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Sat, 2 Aug 2025 09:04:30 +0900 Subject: [PATCH 16/41] Update colors --- lib/properties/color.js | 16 ++++++++++++---- lib/properties/floodColor.js | 16 ++++++++++++---- lib/properties/lightingColor.js | 16 ++++++++++++---- lib/properties/outlineColor.js | 16 ++++++++++++---- lib/properties/stopColor.js | 16 ++++++++++++---- 5 files changed, 60 insertions(+), 20 deletions(-) diff --git a/lib/properties/color.js b/lib/properties/color.js index 9849d5d4..d5f375c0 100644 --- a/lib/properties/color.js +++ b/lib/properties/color.js @@ -1,13 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const val = parsers.parseColor(v); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("color", v)) { + const val = parsers.parseColor(v); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v); }; module.exports.definition = { diff --git a/lib/properties/floodColor.js b/lib/properties/floodColor.js index e98834c8..9e4377be 100644 --- a/lib/properties/floodColor.js +++ b/lib/properties/floodColor.js @@ -1,13 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const val = parsers.parseColor(v); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("flood-color", v)) { + const val = parsers.parseColor(v); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v); }; module.exports.definition = { diff --git a/lib/properties/lightingColor.js b/lib/properties/lightingColor.js index 153124e0..f93846c2 100644 --- a/lib/properties/lightingColor.js +++ b/lib/properties/lightingColor.js @@ -1,13 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const val = parsers.parseColor(v); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("lighting-color", v)) { + const val = parsers.parseColor(v); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v); }; module.exports.definition = { diff --git a/lib/properties/outlineColor.js b/lib/properties/outlineColor.js index 5677be66..3e83eb49 100644 --- a/lib/properties/outlineColor.js +++ b/lib/properties/outlineColor.js @@ -1,13 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const val = parsers.parseColor(v); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("outline-color", v)) { + const val = parsers.parseColor(v); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v); }; module.exports.definition = { diff --git a/lib/properties/stopColor.js b/lib/properties/stopColor.js index 3a88e1bb..66a50bf6 100644 --- a/lib/properties/stopColor.js +++ b/lib/properties/stopColor.js @@ -1,13 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const val = parsers.parseColor(v); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("stop-color", v)) { + const val = parsers.parseColor(v); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v); }; module.exports.definition = { From 9c87c5ac7deacb57c62867835b01bea23d2c9caf Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Sat, 2 Aug 2025 09:14:50 +0900 Subject: [PATCH 17/41] Update positions and boxes --- lib/properties/bottom.js | 16 ++++++++++++---- lib/properties/height.js | 17 ++++++++++++----- lib/properties/left.js | 16 ++++++++++++---- lib/properties/right.js | 16 ++++++++++++---- lib/properties/top.js | 16 ++++++++++++---- lib/properties/width.js | 17 ++++++++++++----- 6 files changed, 72 insertions(+), 26 deletions(-) diff --git a/lib/properties/bottom.js b/lib/properties/bottom.js index 862a9507..5c065819 100644 --- a/lib/properties/bottom.js +++ b/lib/properties/bottom.js @@ -1,13 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const dim = parsers.parseMeasurement(v); - if (dim) { - return dim; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("bottom", v)) { + const dim = parsers.parseMeasurement(v); + if (dim) { + return dim; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v, ["auto"]); }; module.exports.definition = { diff --git a/lib/properties/height.js b/lib/properties/height.js index 0a2e11dd..00463585 100644 --- a/lib/properties/height.js +++ b/lib/properties/height.js @@ -1,14 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const dim = parsers.parseMeasurement(v, true); - if (dim) { - return dim; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("height", v)) { + const dim = parsers.parseMeasurement(v, true); + if (dim) { + return dim; + } + return strings.asciiLowercase(v); } - const keywords = ["auto", "min-content", "max-content", "fit-content"]; - return parsers.parseKeyword(v, keywords); }; module.exports.definition = { diff --git a/lib/properties/left.js b/lib/properties/left.js index c09b3c30..1b534bdf 100644 --- a/lib/properties/left.js +++ b/lib/properties/left.js @@ -1,13 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const dim = parsers.parseMeasurement(v); - if (dim) { - return dim; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("left", v)) { + const dim = parsers.parseMeasurement(v); + if (dim) { + return dim; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v, ["auto"]); }; module.exports.definition = { diff --git a/lib/properties/right.js b/lib/properties/right.js index 408c84a8..f89a2601 100644 --- a/lib/properties/right.js +++ b/lib/properties/right.js @@ -1,13 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const dim = parsers.parseMeasurement(v); - if (dim) { - return dim; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("right", v)) { + const dim = parsers.parseMeasurement(v); + if (dim) { + return dim; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v, ["auto"]); }; module.exports.definition = { diff --git a/lib/properties/top.js b/lib/properties/top.js index 5cee254b..d0c1a956 100644 --- a/lib/properties/top.js +++ b/lib/properties/top.js @@ -1,13 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const dim = parsers.parseMeasurement(v); - if (dim) { - return dim; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("top", v)) { + const dim = parsers.parseMeasurement(v); + if (dim) { + return dim; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v, ["auto"]); }; module.exports.definition = { diff --git a/lib/properties/width.js b/lib/properties/width.js index 59e937f2..da2a034b 100644 --- a/lib/properties/width.js +++ b/lib/properties/width.js @@ -1,14 +1,21 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const dim = parsers.parseMeasurement(v, true); - if (dim) { - return dim; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("width", v)) { + const dim = parsers.parseMeasurement(v, true); + if (dim) { + return dim; + } + return strings.asciiLowercase(v); } - const keywords = ["auto", "min-content", "max-content", "fit-content"]; - return parsers.parseKeyword(v, keywords); }; module.exports.definition = { From a117362288fbaf4b58683c3ecc979fbd8336bce2 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Sat, 2 Aug 2025 11:29:00 +0900 Subject: [PATCH 18/41] Update floats and display --- lib/properties/clear.js | 21 ++++++--------------- lib/properties/display.js | 3 +++ lib/properties/float.js | 8 ++++++-- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/properties/clear.js b/lib/properties/clear.js index 77f4be03..814d8d98 100644 --- a/lib/properties/clear.js +++ b/lib/properties/clear.js @@ -1,23 +1,14 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const keywords = [ - "inline-start", - "inline-end", - "block-start", - "block-end", - "left", - "right", - "top", - "bottom", - "both-inline", - "both-block", - "both", - "none" - ]; - return parsers.parseKeyword(v, keywords); + if (v === "") { + return v; + } else if (parsers.isValidPropertyValue("clear", v)) { + return strings.asciiLowercase(v); + } }; module.exports.definition = { diff --git a/lib/properties/display.js b/lib/properties/display.js index 2d5ee51c..a97fc0be 100644 --- a/lib/properties/display.js +++ b/lib/properties/display.js @@ -36,6 +36,9 @@ module.exports.parse = function parse(v) { if (v === "") { return v; } + if (!parsers.isValidPropertyValue("display", v)) { + return; + } const values = parsers.splitValue(v); switch (values.length) { case 1: { diff --git a/lib/properties/float.js b/lib/properties/float.js index a2622691..e52b1350 100644 --- a/lib/properties/float.js +++ b/lib/properties/float.js @@ -1,10 +1,14 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - const keywords = ["left", "right", "none", "inline-start", "inline-end"]; - return parsers.parseKeyword(v, keywords); + if (v === "") { + return v; + } else if (parsers.isValidPropertyValue("float", v)) { + return strings.asciiLowercase(v); + } }; module.exports.definition = { From ae15a829fa918662482da8fefa017f1d299c58f7 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Sat, 2 Aug 2025 11:57:57 +0900 Subject: [PATCH 19/41] Fix colors and opacity --- lib/properties/borderBottomColor.js | 3 +-- lib/properties/borderColor.js | 3 +-- lib/properties/borderLeftColor.js | 3 +-- lib/properties/borderRightColor.js | 3 +-- lib/properties/borderTopColor.js | 3 +-- lib/properties/color.js | 3 +-- lib/properties/floodColor.js | 3 +-- lib/properties/lightingColor.js | 3 +-- lib/properties/opacity.js | 27 ++++++++------------------ lib/properties/outlineColor.js | 3 +-- test/properties.test.js | 30 ++++++++++++++++++++++------- 11 files changed, 40 insertions(+), 44 deletions(-) diff --git a/lib/properties/borderBottomColor.js b/lib/properties/borderBottomColor.js index aab19722..8839a1ca 100644 --- a/lib/properties/borderBottomColor.js +++ b/lib/properties/borderBottomColor.js @@ -1,7 +1,6 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); module.exports.parse = function parse(v) { if (v === "") { @@ -14,7 +13,7 @@ module.exports.parse = function parse(v) { if (val) { return val; } - return strings.asciiLowercase(v); + return parsers.parseKeyword(v); } }; diff --git a/lib/properties/borderColor.js b/lib/properties/borderColor.js index 3892dd66..7583bd07 100644 --- a/lib/properties/borderColor.js +++ b/lib/properties/borderColor.js @@ -1,7 +1,6 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); module.exports.parse = function parse(v) { if (v === "") { @@ -14,7 +13,7 @@ module.exports.parse = function parse(v) { if (val) { return val; } - return strings.asciiLowercase(v); + return parsers.parseKeyword(v); } }; diff --git a/lib/properties/borderLeftColor.js b/lib/properties/borderLeftColor.js index f5183936..8103dff3 100644 --- a/lib/properties/borderLeftColor.js +++ b/lib/properties/borderLeftColor.js @@ -1,7 +1,6 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); module.exports.parse = function parse(v) { if (v === "") { @@ -14,7 +13,7 @@ module.exports.parse = function parse(v) { if (val) { return val; } - return strings.asciiLowercase(v); + return parsers.parseKeyword(v); } }; diff --git a/lib/properties/borderRightColor.js b/lib/properties/borderRightColor.js index f69b7c1a..bd9906c6 100644 --- a/lib/properties/borderRightColor.js +++ b/lib/properties/borderRightColor.js @@ -1,7 +1,6 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); module.exports.parse = function parse(v) { if (v === "") { @@ -14,7 +13,7 @@ module.exports.parse = function parse(v) { if (val) { return val; } - return strings.asciiLowercase(v); + return parsers.parseKeyword(v); } }; diff --git a/lib/properties/borderTopColor.js b/lib/properties/borderTopColor.js index 6e9eda98..f675d99f 100644 --- a/lib/properties/borderTopColor.js +++ b/lib/properties/borderTopColor.js @@ -1,7 +1,6 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); module.exports.parse = function parse(v) { if (v === "") { @@ -14,7 +13,7 @@ module.exports.parse = function parse(v) { if (val) { return val; } - return strings.asciiLowercase(v); + return parsers.parseKeyword(v); } }; diff --git a/lib/properties/color.js b/lib/properties/color.js index d5f375c0..3b581c93 100644 --- a/lib/properties/color.js +++ b/lib/properties/color.js @@ -1,7 +1,6 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); module.exports.parse = function parse(v) { if (v === "") { @@ -14,7 +13,7 @@ module.exports.parse = function parse(v) { if (val) { return val; } - return strings.asciiLowercase(v); + return parsers.parseKeyword(v); } }; diff --git a/lib/properties/floodColor.js b/lib/properties/floodColor.js index 9e4377be..7a0cdf77 100644 --- a/lib/properties/floodColor.js +++ b/lib/properties/floodColor.js @@ -1,7 +1,6 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); module.exports.parse = function parse(v) { if (v === "") { @@ -14,7 +13,7 @@ module.exports.parse = function parse(v) { if (val) { return val; } - return strings.asciiLowercase(v); + return parsers.parseKeyword(v); } }; diff --git a/lib/properties/lightingColor.js b/lib/properties/lightingColor.js index f93846c2..76978ed5 100644 --- a/lib/properties/lightingColor.js +++ b/lib/properties/lightingColor.js @@ -1,7 +1,6 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); module.exports.parse = function parse(v) { if (v === "") { @@ -14,7 +13,7 @@ module.exports.parse = function parse(v) { if (val) { return val; } - return strings.asciiLowercase(v); + return parsers.parseKeyword(v); } }; diff --git a/lib/properties/opacity.js b/lib/properties/opacity.js index 23c454ed..39c81121 100644 --- a/lib/properties/opacity.js +++ b/lib/properties/opacity.js @@ -1,29 +1,18 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); module.exports.parse = function parse(v) { - let num = parsers.parseNumber(v); - if (num) { - num = parseFloat(num); - if (num < 0) { - return "0"; - } else if (num > 1) { - return "1"; + if (v === "") { + return v; + } else if (parsers.isValidPropertyValue("opacity", v)) { + const val = parsers.parseNumber(v) ?? parsers.parsePercent(v); + if (val) { + return val; } - return `${num}`; + return strings.asciiLowercase(v); } - let pct = parsers.parsePercent(v); - if (pct) { - pct = parseFloat(pct); - if (pct < 0) { - return "0%"; - } else if (pct > 100) { - return "100%"; - } - return `${pct}%`; - } - return parsers.parseKeyword(v); }; module.exports.definition = { diff --git a/lib/properties/outlineColor.js b/lib/properties/outlineColor.js index 3e83eb49..c7166971 100644 --- a/lib/properties/outlineColor.js +++ b/lib/properties/outlineColor.js @@ -1,7 +1,6 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); module.exports.parse = function parse(v) { if (v === "") { @@ -14,7 +13,7 @@ module.exports.parse = function parse(v) { if (val) { return val; } - return strings.asciiLowercase(v); + return parsers.parseKeyword(v); } }; diff --git a/test/properties.test.js b/test/properties.test.js index 8023655d..b049ca15 100644 --- a/test/properties.test.js +++ b/test/properties.test.js @@ -1315,6 +1315,18 @@ describe("color", () => { ); }); + it("color should not should set / get invalid value", () => { + testPropertyValue( + "color", + "color-mix(in hsl, hsl(120deg 10% 20%) 0%, hsl(30deg 30% 40%) 0%)", + "" + ); + }); + + it("color should not should set / get invalid value", () => { + testPropertyValue("color", "color(srgb 0 0 0 0)", ""); + }); + it("opacity should set / get keyword", () => { testPropertyValue("opacity", "inherit", "inherit"); }); @@ -1324,23 +1336,27 @@ describe("color", () => { }); it("opacity should set / get number", () => { - testPropertyValue("opacity", "1.5", "1"); + testPropertyValue("opacity", ".5", "0.5"); }); - it("opacity should set / get clamped number", () => { - testPropertyValue("opacity", "-1", "0"); + it("opacity should set / get number", () => { + testPropertyValue("opacity", "1.5", "1.5"); + }); + + it("opacity should set / get number", () => { + testPropertyValue("opacity", "-1", "-1"); }); it("opacity should set / get percent", () => { testPropertyValue("opacity", "50%", "50%"); }); - it("opacity should set / get clamped percent", () => { - testPropertyValue("opacity", "150%", "100%"); + it("opacity should set / get percent", () => { + testPropertyValue("opacity", "150%", "150%"); }); - it("opacity should set / get clamped percent", () => { - testPropertyValue("opacity", "-50%", "0%"); + it("opacity should set / get percent", () => { + testPropertyValue("opacity", "-50%", "-50%"); }); }); From 7b1acaffdb768d5d09812de375f3896f2c6130e5 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Sat, 2 Aug 2025 12:17:29 +0900 Subject: [PATCH 20/41] Rename variable --- lib/properties/borderSpacing.js | 10 +++++----- lib/properties/bottom.js | 6 +++--- lib/properties/fontWeight.js | 8 ++++---- lib/properties/height.js | 6 +++--- lib/properties/left.js | 6 +++--- lib/properties/right.js | 6 +++--- lib/properties/top.js | 6 +++--- lib/properties/width.js | 6 +++--- test/properties.test.js | 8 ++++++++ 9 files changed, 35 insertions(+), 27 deletions(-) diff --git a/lib/properties/borderSpacing.js b/lib/properties/borderSpacing.js index 4b282c5b..d0583a4b 100644 --- a/lib/properties/borderSpacing.js +++ b/lib/properties/borderSpacing.js @@ -19,15 +19,15 @@ module.exports.parse = function parse(v) { if (!parts.length || parts.length > 2) { return; } - const val = []; + const values = []; for (const part of parts) { - const dim = parsers.parseLength(part); - if (!dim) { + const val = parsers.parseLength(part); + if (!val) { return; } - val.push(dim); + values.push(val); } - return val.join(" "); + return values.join(" "); }; module.exports.definition = { diff --git a/lib/properties/bottom.js b/lib/properties/bottom.js index 5c065819..109cdd6f 100644 --- a/lib/properties/bottom.js +++ b/lib/properties/bottom.js @@ -10,9 +10,9 @@ module.exports.parse = function parse(v) { v = parsers.parseCalc(v); } if (parsers.isValidPropertyValue("bottom", v)) { - const dim = parsers.parseMeasurement(v); - if (dim) { - return dim; + const val = parsers.parseMeasurement(v); + if (val) { + return val; } return strings.asciiLowercase(v); } diff --git a/lib/properties/fontWeight.js b/lib/properties/fontWeight.js index aaead851..cb17034b 100644 --- a/lib/properties/fontWeight.js +++ b/lib/properties/fontWeight.js @@ -10,12 +10,12 @@ module.exports.parse = function parse(v) { v = parsers.parseCalc(v); } if (parsers.isValidPropertyValue("font-weight", v)) { - const num = parsers.parseNumber(v, true); - if (num) { - if (num < 1 || parseFloat(num) > 1000) { + const val = parsers.parseNumber(v, true); + if (val) { + if (val < 1 || parseFloat(val) > 1000) { return; } - return num; + return val; } return strings.asciiLowercase(v); } diff --git a/lib/properties/height.js b/lib/properties/height.js index 00463585..572cc468 100644 --- a/lib/properties/height.js +++ b/lib/properties/height.js @@ -10,9 +10,9 @@ module.exports.parse = function parse(v) { v = parsers.parseCalc(v); } if (parsers.isValidPropertyValue("height", v)) { - const dim = parsers.parseMeasurement(v, true); - if (dim) { - return dim; + const val = parsers.parseMeasurement(v, true); + if (val) { + return val; } return strings.asciiLowercase(v); } diff --git a/lib/properties/left.js b/lib/properties/left.js index 1b534bdf..8605fe94 100644 --- a/lib/properties/left.js +++ b/lib/properties/left.js @@ -10,9 +10,9 @@ module.exports.parse = function parse(v) { v = parsers.parseCalc(v); } if (parsers.isValidPropertyValue("left", v)) { - const dim = parsers.parseMeasurement(v); - if (dim) { - return dim; + const val = parsers.parseMeasurement(v); + if (val) { + return val; } return strings.asciiLowercase(v); } diff --git a/lib/properties/right.js b/lib/properties/right.js index f89a2601..8b700d0a 100644 --- a/lib/properties/right.js +++ b/lib/properties/right.js @@ -10,9 +10,9 @@ module.exports.parse = function parse(v) { v = parsers.parseCalc(v); } if (parsers.isValidPropertyValue("right", v)) { - const dim = parsers.parseMeasurement(v); - if (dim) { - return dim; + const val = parsers.parseMeasurement(v); + if (val) { + return val; } return strings.asciiLowercase(v); } diff --git a/lib/properties/top.js b/lib/properties/top.js index d0c1a956..86b71f5a 100644 --- a/lib/properties/top.js +++ b/lib/properties/top.js @@ -10,9 +10,9 @@ module.exports.parse = function parse(v) { v = parsers.parseCalc(v); } if (parsers.isValidPropertyValue("top", v)) { - const dim = parsers.parseMeasurement(v); - if (dim) { - return dim; + const val = parsers.parseMeasurement(v); + if (val) { + return val; } return strings.asciiLowercase(v); } diff --git a/lib/properties/width.js b/lib/properties/width.js index da2a034b..2eeb573b 100644 --- a/lib/properties/width.js +++ b/lib/properties/width.js @@ -10,9 +10,9 @@ module.exports.parse = function parse(v) { v = parsers.parseCalc(v); } if (parsers.isValidPropertyValue("width", v)) { - const dim = parsers.parseMeasurement(v, true); - if (dim) { - return dim; + const val = parsers.parseMeasurement(v, true); + if (val) { + return val; } return strings.asciiLowercase(v); } diff --git a/test/properties.test.js b/test/properties.test.js index b049ca15..243e42ca 100644 --- a/test/properties.test.js +++ b/test/properties.test.js @@ -1257,6 +1257,10 @@ describe("box sizing", () => { testPropertyValue("height", "auto", "auto"); }); + it("height should set / get length", () => { + testPropertyValue("height", "0", "0px"); + }); + it("height should set / get length", () => { testPropertyValue("height", "10px", "10px"); }); @@ -1277,6 +1281,10 @@ describe("box sizing", () => { testPropertyValue("width", "auto", "auto"); }); + it("height should set / get length", () => { + testPropertyValue("width", "0", "0px"); + }); + it("width should set / get length", () => { testPropertyValue("width", "10px", "10px"); }); From 79ae451efe6f3ae778911431c0e126ececf29b6b Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Sat, 2 Aug 2025 12:29:53 +0900 Subject: [PATCH 21/41] Update margins and paddings --- lib/properties/margin.js | 16 ++++++++++++---- lib/properties/marginBottom.js | 25 +++++++++++++++---------- lib/properties/marginLeft.js | 25 +++++++++++++++---------- lib/properties/marginRight.js | 25 +++++++++++++++---------- lib/properties/marginTop.js | 25 +++++++++++++++---------- lib/properties/padding.js | 16 ++++++++++++---- lib/properties/paddingBottom.js | 25 +++++++++++++++---------- lib/properties/paddingLeft.js | 25 +++++++++++++++---------- lib/properties/paddingRight.js | 25 +++++++++++++++---------- lib/properties/paddingTop.js | 25 +++++++++++++++---------- 10 files changed, 144 insertions(+), 88 deletions(-) diff --git a/lib/properties/margin.js b/lib/properties/margin.js index 84f02c53..ce469c02 100644 --- a/lib/properties/margin.js +++ b/lib/properties/margin.js @@ -1,15 +1,23 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); const positions = ["top", "right", "bottom", "left"]; module.exports.parse = function parse(v) { - const val = parsers.parseMeasurement(v); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("margin", v)) { + const val = parsers.parseMeasurement(v); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v, ["auto"]); }; module.exports.definition = { diff --git a/lib/properties/marginBottom.js b/lib/properties/marginBottom.js index b41c4345..a0145a1c 100644 --- a/lib/properties/marginBottom.js +++ b/lib/properties/marginBottom.js @@ -1,13 +1,23 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); + +const positions = ["top", "right", "bottom", "left"]; module.exports.parse = function parse(v) { - const val = parsers.parseMeasurement(v); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("margin-bottom", v)) { + const val = parsers.parseMeasurement(v); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v, ["auto"]); }; module.exports.definition = { @@ -17,12 +27,7 @@ module.exports.definition = { this._setProperty("margin", ""); this._setProperty("margin-bottom", v); } else { - this._subImplicitSetter("margin", "bottom", v, module.exports.parse, [ - "top", - "right", - "bottom", - "left" - ]); + this._subImplicitSetter("margin", "bottom", v, module.exports.parse, positions); } }, get() { diff --git a/lib/properties/marginLeft.js b/lib/properties/marginLeft.js index 34930028..c967b4e1 100644 --- a/lib/properties/marginLeft.js +++ b/lib/properties/marginLeft.js @@ -1,13 +1,23 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); + +const positions = ["top", "right", "bottom", "left"]; module.exports.parse = function parse(v) { - const val = parsers.parseMeasurement(v); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("margin-left", v)) { + const val = parsers.parseMeasurement(v); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v, ["auto"]); }; module.exports.definition = { @@ -17,12 +27,7 @@ module.exports.definition = { this._setProperty("margin", ""); this._setProperty("margin-left", v); } else { - this._subImplicitSetter("margin", "left", v, module.exports.parse, [ - "top", - "right", - "bottom", - "left" - ]); + this._subImplicitSetter("margin", "left", v, module.exports.parse, positions); } }, get() { diff --git a/lib/properties/marginRight.js b/lib/properties/marginRight.js index 437bc808..dac2e3c8 100644 --- a/lib/properties/marginRight.js +++ b/lib/properties/marginRight.js @@ -1,13 +1,23 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); + +const positions = ["top", "right", "bottom", "left"]; module.exports.parse = function parse(v) { - const val = parsers.parseMeasurement(v); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("margin-right", v)) { + const val = parsers.parseMeasurement(v); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v, ["auto"]); }; module.exports.definition = { @@ -17,12 +27,7 @@ module.exports.definition = { this._setProperty("margin", ""); this._setProperty("margin-right", v); } else { - this._subImplicitSetter("margin", "right", v, module.exports.parse, [ - "top", - "right", - "bottom", - "left" - ]); + this._subImplicitSetter("margin", "right", v, module.exports.parse, positions); } }, get() { diff --git a/lib/properties/marginTop.js b/lib/properties/marginTop.js index dab96396..eb473b00 100644 --- a/lib/properties/marginTop.js +++ b/lib/properties/marginTop.js @@ -1,13 +1,23 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); + +const positions = ["top", "right", "bottom", "left"]; module.exports.parse = function parse(v) { - const val = parsers.parseMeasurement(v); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("margin-top", v)) { + const val = parsers.parseMeasurement(v); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v, ["auto"]); }; module.exports.definition = { @@ -17,12 +27,7 @@ module.exports.definition = { this._setProperty("margin", ""); this._setProperty("margin-top", v); } else { - this._subImplicitSetter("margin", "top", v, module.exports.parse, [ - "top", - "right", - "bottom", - "left" - ]); + this._subImplicitSetter("margin", "top", v, module.exports.parse, positions); } }, get() { diff --git a/lib/properties/padding.js b/lib/properties/padding.js index 7ce3b1de..f0112c27 100644 --- a/lib/properties/padding.js +++ b/lib/properties/padding.js @@ -1,15 +1,23 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); const positions = ["top", "right", "bottom", "left"]; module.exports.parse = function parse(v) { - const val = parsers.parseMeasurement(v, true); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("padding", v)) { + const val = parsers.parseMeasurement(v, true); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v); }; module.exports.definition = { diff --git a/lib/properties/paddingBottom.js b/lib/properties/paddingBottom.js index 7de58d06..8d7b9f66 100644 --- a/lib/properties/paddingBottom.js +++ b/lib/properties/paddingBottom.js @@ -1,13 +1,23 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); + +const positions = ["top", "right", "bottom", "left"]; module.exports.parse = function parse(v) { - const val = parsers.parseMeasurement(v, true); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("padding-bottom", v)) { + const val = parsers.parseMeasurement(v, true); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v); }; module.exports.definition = { @@ -17,12 +27,7 @@ module.exports.definition = { this._setProperty("padding", ""); this._setProperty("padding-bottom", v); } else { - this._subImplicitSetter("padding", "bottom", v, module.exports.parse, [ - "top", - "right", - "bottom", - "left" - ]); + this._subImplicitSetter("padding", "bottom", v, module.exports.parse, positions); } }, get() { diff --git a/lib/properties/paddingLeft.js b/lib/properties/paddingLeft.js index 046f513d..9beaaefe 100644 --- a/lib/properties/paddingLeft.js +++ b/lib/properties/paddingLeft.js @@ -1,13 +1,23 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); + +const positions = ["top", "right", "bottom", "left"]; module.exports.parse = function parse(v) { - const val = parsers.parseMeasurement(v, true); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("padding-left", v)) { + const val = parsers.parseMeasurement(v, true); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v); }; module.exports.definition = { @@ -17,12 +27,7 @@ module.exports.definition = { this._setProperty("padding", ""); this._setProperty("padding-left", v); } else { - this._subImplicitSetter("padding", "left", v, module.exports.parse, [ - "top", - "right", - "bottom", - "left" - ]); + this._subImplicitSetter("padding", "left", v, module.exports.parse, positions); } }, get() { diff --git a/lib/properties/paddingRight.js b/lib/properties/paddingRight.js index 7109933a..a3d301a4 100644 --- a/lib/properties/paddingRight.js +++ b/lib/properties/paddingRight.js @@ -1,13 +1,23 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); + +const positions = ["top", "right", "bottom", "left"]; module.exports.parse = function parse(v) { - const val = parsers.parseMeasurement(v, true); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("padding-right", v)) { + const val = parsers.parseMeasurement(v, true); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v); }; module.exports.definition = { @@ -17,12 +27,7 @@ module.exports.definition = { this._setProperty("padding", ""); this._setProperty("padding-right", v); } else { - this._subImplicitSetter("padding", "right", v, module.exports.parse, [ - "top", - "right", - "bottom", - "left" - ]); + this._subImplicitSetter("padding", "right", v, module.exports.parse, positions); } }, get() { diff --git a/lib/properties/paddingTop.js b/lib/properties/paddingTop.js index b12a19f2..2e23b8eb 100644 --- a/lib/properties/paddingTop.js +++ b/lib/properties/paddingTop.js @@ -1,13 +1,23 @@ "use strict"; const parsers = require("../parsers"); +const strings = require("../utils/strings"); + +const positions = ["top", "right", "bottom", "left"]; module.exports.parse = function parse(v) { - const val = parsers.parseMeasurement(v, true); - if (val) { - return val; + if (v === "") { + return v; + } else if (parsers.hasCalcFunc(v)) { + v = parsers.parseCalc(v); + } + if (parsers.isValidPropertyValue("padding-top", v)) { + const val = parsers.parseMeasurement(v, true); + if (val) { + return val; + } + return strings.asciiLowercase(v); } - return parsers.parseKeyword(v); }; module.exports.definition = { @@ -17,12 +27,7 @@ module.exports.definition = { this._setProperty("padding", ""); this._setProperty("padding-top", v); } else { - this._subImplicitSetter("padding", "top", v, module.exports.parse, [ - "top", - "right", - "bottom", - "left" - ]); + this._subImplicitSetter("padding", "top", v, module.exports.parse, positions); } }, get() { From bd512f63b1d2e664eb2740db1e07b2a8cffda7ef Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Sat, 2 Aug 2025 12:38:50 +0900 Subject: [PATCH 22/41] Update parsers.js --- lib/parsers.js | 431 ++++++++++++++++++++----------------------- test/parsers.test.js | 26 +-- 2 files changed, 208 insertions(+), 249 deletions(-) diff --git a/lib/parsers.js b/lib/parsers.js index 22ec845d..c9e8b527 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -1,7 +1,3 @@ -/** - * These are commonly used parsers for CSS Values they take a string to parse - * and return a string after it's been converted, if needed - */ "use strict"; const { @@ -16,15 +12,6 @@ const { asciiLowercase } = require("./utils/strings"); // @see https://drafts.csswg.org/css-cascade-5/#defaulting-keywords const GLOBAL_VALUE = Object.freeze(["initial", "inherit", "unset", "revert", "revert-layer"]); -// Numeric data types -const NUM_TYPE = Object.freeze({ - UNDEFINED: 0, - NUMBER: 1, - PERCENT: 2, - LENGTH: 4, - ANGLE: 8 -}); - // System colors // @see https://drafts.csswg.org/css-color/#css-system-colors // @see https://drafts.csswg.org/css-color/#deprecated-system-colors @@ -74,42 +61,15 @@ const SYS_COLOR = Object.freeze([ ]); // Regular expressions -const DIGIT = "(?:0|[1-9]\\d*)"; -const NUMBER = `[+-]?(?:${DIGIT}(?:\\.\\d*)?|\\.\\d+)(?:e-?${DIGIT})?`; -const CALC_KEY = +const CALC_FUNC_NAMES = "(?:a?(?:cos|sin|tan)|abs|atan2|calc|clamp|exp|hypot|log|max|min|mod|pow|rem|round|sign|sqrt)"; -const unitRegEx = new RegExp(`^(${NUMBER})([a-z]+|%)?$`, "i"); -const urlRegEx = /^url\(\s*((?:[^)]|\\\))*)\s*\)$/; -const keywordRegEx = /^[a-z]+(?:-[a-z]+)*$/i; -const stringRegEx = /^("[^"]*"|'[^']*')$/; const varRegEx = /^var\(/; const varContainedRegEx = /(?<=[*/\s(])var\(/; -const calcRegEx = new RegExp(`^${CALC_KEY}\\(`); -const calcNameRegEx = new RegExp(`^${CALC_KEY}$`); -const calcContainedRegEx = new RegExp(`(?<=[*/\\s(])${CALC_KEY}\\(`); -const gradientRegEx = /^(?:repeating-)?(?:conic|linear|radial)-gradient\(/; -const functionRegEx = /^([a-z][a-z\d]*(?:-[a-z\d]+)*)\(/i; - -const getNumericType = function getNumericType(val) { - if (unitRegEx.test(val)) { - const [, , unit] = unitRegEx.exec(val); - if (!unit) { - return NUM_TYPE.NUMBER; - } - if (unit === "%") { - return NUM_TYPE.PERCENT; - } - if (/^(?:[cm]m|[dls]?v(?:[bhiw]|max|min)|in|p[ctx]|q|r?(?:[cl]h|cap|e[mx]|ic))$/i.test(unit)) { - return NUM_TYPE.LENGTH; - } - if (/^(?:deg|g?rad|turn)$/i.test(unit)) { - return NUM_TYPE.ANGLE; - } - } - return NUM_TYPE.UNDEFINED; -}; +const calcRegEx = new RegExp(`^${CALC_FUNC_NAMES}\\(`); +const calcContainedRegEx = new RegExp(`(?<=[*/\\s(])${CALC_FUNC_NAMES}\\(`); +const calcNameRegEx = new RegExp(`^${CALC_FUNC_NAMES}$`); -// Patch css-tree +// Patch css-tree. const cssTree = csstree.fork(syntaxes); // Prepare stringified value. @@ -139,12 +99,12 @@ exports.prepareValue = function prepareValue(value, globalObject = globalThis) { } }; -// Value starts with and/or contains CSS var() function +// Value starts with and/or contains CSS var() function. exports.hasVarFunc = function hasVarFunc(val) { return varRegEx.test(val) || varContainedRegEx.test(val); }; -// Value starts with and/or contains CSS calc() related functions +// Value starts with and/or contains CSS calc() related functions. exports.hasCalcFunc = function hasCalcFunc(val) { return calcRegEx.test(val) || calcContainedRegEx.test(val); }; @@ -153,8 +113,11 @@ exports.hasCalcFunc = function hasCalcFunc(val) { // @see https://github.com/asamuzaK/cssColor/blob/main/src/js/util.ts exports.splitValue = splitValue; -// Parse CSS to AST or Object +// Parse CSS to AST or Object. exports.parseCSS = function parseCSS(val, opt, toObject = false) { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } const ast = cssTree.parse(val, opt); if (toObject) { return cssTree.toPlainObject(ast); @@ -164,6 +127,9 @@ exports.parseCSS = function parseCSS(val, opt, toObject = false) { // Returns `false` for custom property and/or var(). exports.isValidPropertyValue = function isValidPropertyValue(prop, val) { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } if (val === "") { return true; } @@ -188,6 +154,9 @@ exports.isValidPropertyValue = function isValidPropertyValue(prop, val) { }; exports.parseCalc = function parseCalc(val, opt = { format: "specifiedValue" }) { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } if (val === "" || exports.hasVarFunc(val) || !exports.hasCalcFunc(val)) { return val; } @@ -200,7 +169,10 @@ exports.parseCalc = function parseCalc(val, opt = { format: "specifiedValue" }) for (const item of items) { const { type: itemType, name: itemName, value: itemValue } = item; if (itemType === "Function") { - const value = cssTree.generate(item).replaceAll(")", ") ").trim(); + const value = cssTree + .generate(item) + .replace(/\)(?!\)|\s|,)/g, ") ") + .trim(); if (calcNameRegEx.test(itemName)) { const newValue = cssCalc(value, opt); values.push(newValue); @@ -216,7 +188,44 @@ exports.parseCalc = function parseCalc(val, opt = { format: "specifiedValue" }) return values.join(" "); }; +exports.parseFunction = function parseFunction(val) { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } + if (val === "") { + return { + name: null, + value: "" + }; + } + const obj = exports.parseCSS(val, { context: "value" }, true); + if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + return; + } + const [{ name, type }] = obj.children; + if (type !== "Function") { + return; + } + if (/^var$/.test(name) || exports.hasVarFunc(val)) { + return { + name: "var", + value: val + }; + } + const value = val + .replace(new RegExp(`^${name}\\(`), "") + .replace(/\)$/, "") + .trim(); + return { + name, + value + }; +}; + exports.parseNumber = function parseNumber(val, restrictToPositive = false) { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } if (val === "" || exports.hasVarFunc(val)) { return val; } else if (exports.hasCalcFunc(val)) { @@ -224,17 +233,25 @@ exports.parseNumber = function parseNumber(val, restrictToPositive = false) { format: "specifiedValue" }); } - const type = getNumericType(val); - if (type === NUM_TYPE.NUMBER) { - const num = parseFloat(val); - if (restrictToPositive && num < 0) { - return; - } - return `${num}`; + const obj = exports.parseCSS(val, { context: "value" }, true); + if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + return; + } + const [{ type, value }] = obj.children; + if (type !== "Number") { + return; } + const num = parseFloat(value); + if (restrictToPositive && num < 0) { + return; + } + return `${num}`; }; exports.parseLength = function parseLength(val, restrictToPositive = false) { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } if (val === "" || exports.hasVarFunc(val)) { return val; } else if (exports.hasCalcFunc(val)) { @@ -242,27 +259,29 @@ exports.parseLength = function parseLength(val, restrictToPositive = false) { format: "specifiedValue" }); } - const type = getNumericType(val); - switch (type) { - case NUM_TYPE.NUMBER: { - if (parseFloat(val) === 0) { - return "0px"; - } - break; - } - case NUM_TYPE.LENGTH: { - const [, numVal, unit] = unitRegEx.exec(val); - const num = parseFloat(numVal); - if (restrictToPositive && num < 0) { - return; - } - return `${num}${asciiLowercase(unit)}`; - } - default: + const obj = exports.parseCSS(val, { context: "value" }, true); + if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + return; + } + const [{ type, unit, value }] = obj.children; + if (type !== "Dimension" && type !== "Number") { + return; + } + const num = parseFloat(value); + if (restrictToPositive && num < 0) { + return; + } + if (num === 0 && !unit) { + return `${num}px`; + } else if (unit) { + return `${num}${asciiLowercase(unit)}`; } }; exports.parsePercent = function parsePercent(val, restrictToPositive = false) { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } if (val === "" || exports.hasVarFunc(val)) { return val; } else if (exports.hasCalcFunc(val)) { @@ -270,28 +289,29 @@ exports.parsePercent = function parsePercent(val, restrictToPositive = false) { format: "specifiedValue" }); } - const type = getNumericType(val); - switch (type) { - case NUM_TYPE.NUMBER: { - if (parseFloat(val) === 0) { - return "0%"; - } - break; - } - case NUM_TYPE.PERCENT: { - const [, numVal, unit] = unitRegEx.exec(val); - const num = parseFloat(numVal); - if (restrictToPositive && num < 0) { - return; - } - return `${num}${asciiLowercase(unit)}`; - } - default: + const obj = exports.parseCSS(val, { context: "value" }, true); + if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + return; + } + const [{ type, value }] = obj.children; + if (type !== "Percentage" && type !== "Number") { + return; + } + const num = parseFloat(value); + if (restrictToPositive && num < 0) { + return; + } + if (num === 0) { + return `${num}%`; } + return `${num}%`; }; // Either a length or a percent. exports.parseMeasurement = function parseMeasurement(val, restrictToPositive = false) { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } if (val === "" || exports.hasVarFunc(val)) { return val; } else if (exports.hasCalcFunc(val)) { @@ -299,28 +319,31 @@ exports.parseMeasurement = function parseMeasurement(val, restrictToPositive = f format: "specifiedValue" }); } - const type = getNumericType(val); - switch (type) { - case NUM_TYPE.NUMBER: { - if (parseFloat(val) === 0) { - return "0px"; - } - break; - } - case NUM_TYPE.LENGTH: - case NUM_TYPE.PERCENT: { - const [, numVal, unit] = unitRegEx.exec(val); - const num = parseFloat(numVal); - if (restrictToPositive && num < 0) { - return; - } - return `${num}${asciiLowercase(unit)}`; - } - default: + const obj = exports.parseCSS(val, { context: "value" }, true); + if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + return; + } + const [{ type, unit, value }] = obj.children; + if (type !== "Dimension" && type !== "Number" && type !== "Percentage") { + return; + } + const num = parseFloat(value); + if (restrictToPositive && num < 0) { + return; + } + if (unit) { + return `${num}${asciiLowercase(unit)}`; + } else if (type === "Percentage") { + return `${num}%`; + } else if (num === 0) { + return `${num}px`; } }; exports.parseAngle = function parseAngle(val, normalizeDeg = false) { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } if (val === "" || exports.hasVarFunc(val)) { return val; } else if (exports.hasCalcFunc(val)) { @@ -328,137 +351,100 @@ exports.parseAngle = function parseAngle(val, normalizeDeg = false) { format: "specifiedValue" }); } - const type = getNumericType(val); - switch (type) { - case NUM_TYPE.NUMBER: { - if (parseFloat(val) === 0) { - return "0deg"; - } - break; - } - case NUM_TYPE.ANGLE: { - let [, numVal, unit] = unitRegEx.exec(val); - numVal = parseFloat(numVal); - unit = asciiLowercase(unit); - if (unit === "deg") { - if (normalizeDeg && numVal < 0) { - while (numVal < 0) { - numVal += 360; - } + const obj = exports.parseCSS(val, { context: "value" }, true); + if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + return; + } + const [{ type, unit, value }] = obj.children; + if (type !== "Dimension" && type !== "Number") { + return; + } + let num = parseFloat(value); + if (unit) { + const angle = asciiLowercase(unit); + if (angle === "deg") { + if (normalizeDeg && num < 0) { + while (num < 0) { + num += 360; } - numVal %= 360; } - return `${numVal}${unit}`; + num %= 360; } - default: + return `${num}${angle}`; + } else if (num === 0) { + return `${num}deg`; } }; exports.parseUrl = function parseUrl(val) { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } if (val === "") { return val; } - const res = urlRegEx.exec(val); - if (!res) { + let obj; + try { + obj = exports.parseCSS(val, { context: "value" }, true); + } catch { return; } - let str = res[1]; - // If it starts with single or double quotes, does it end with the same? - if ((str[0] === '"' || str[0] === "'") && str[0] !== str[str.length - 1]) { + if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { return; } - if (str[0] === '"' || str[0] === "'") { - str = str.substr(1, str.length - 2); - } - let urlstr = ""; - let escaped = false; - for (let i = 0; i < str.length; i++) { - switch (str[i]) { - case "\\": - if (escaped) { - urlstr += "\\\\"; - escaped = false; - } else { - escaped = true; - } - break; - case "(": - case ")": - case " ": - case "\t": - case "\n": - case "'": - if (!escaped) { - return; - } - urlstr += str[i]; - escaped = false; - break; - case '"': - if (!escaped) { - return; - } - urlstr += '\\"'; - escaped = false; - break; - default: - urlstr += str[i]; - escaped = false; - } + const [{ type, value }] = obj.children; + if (type !== "Url") { + return; } - return `url("${urlstr}")`; + const str = value.replace(/\\\\/g, "\\").replaceAll('"', '\\"'); + return `url("${str}")`; }; exports.parseString = function parseString(val) { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } if (val === "") { return ""; } - if (!stringRegEx.test(val)) { + const obj = exports.parseCSS(val, { context: "value" }, true); + if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { return; } - val = val.substr(1, val.length - 2); - let str = ""; - let escaped = false; - for (let i = 0; i < val.length; i++) { - switch (val[i]) { - case "\\": - if (escaped) { - str += "\\\\"; - escaped = false; - } else { - escaped = true; - } - break; - case '"': - str += '\\"'; - escaped = false; - break; - default: - str += val[i]; - escaped = false; - } + const [{ type, value }] = obj.children; + if (type !== "String") { + return; } + const str = value.replace(/\\\\/g, "\\").replaceAll('"', '\\"'); return `"${str}"`; }; exports.parseKeyword = function parseKeyword(val, validKeywords = []) { - if (val === "") { - return ""; + if (typeof val !== "string") { + val = exports.prepareValue(val); } - if (varRegEx.test(val)) { + if (val === "" || varRegEx.test(val)) { return val; } - val = asciiLowercase(val); - if (validKeywords.includes(val) || GLOBAL_VALUE.includes(val)) { - return val; + const obj = exports.parseCSS(val, { context: "value" }, true); + if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + return; + } + const [{ name, type }] = obj.children; + if (type !== "Identifier" || name === "undefined") { + return; + } + const value = asciiLowercase(name); + if (validKeywords.includes(value) || GLOBAL_VALUE.includes(value)) { + return value; } }; exports.parseColor = function parseColor(val) { - if (val === "") { - return ""; + if (typeof val !== "string") { + val = exports.prepareValue(val); } - if (varRegEx.test(val)) { + if (val === "" || exports.hasVarFunc(val)) { return val; } if (/^[a-z]+$/i.test(val)) { @@ -473,19 +459,15 @@ exports.parseColor = function parseColor(val) { if (res) { return res; } - return exports.parseKeyword(val); }; exports.parseImage = function parseImage(val) { - if (val === "") { - return ""; + if (typeof val !== "string") { + val = exports.prepareValue(val); } - if (gradientRegEx.test(val) && exports.hasVarFunc(val)) { + if (val === "" || exports.hasVarFunc(val)) { return val; } - if (keywordRegEx.test(val)) { - return exports.parseKeyword(val, ["none"]); - } const res = resolveGradient(val, { format: "specifiedValue" }); @@ -495,33 +477,10 @@ exports.parseImage = function parseImage(val) { return exports.parseUrl(val); }; -exports.parseFunction = function parseFunction(val) { - if (val === "") { - return { - name: null, - value: "" - }; - } - if (functionRegEx.test(val) && val.endsWith(")")) { - if (varRegEx.test(val) || varContainedRegEx.test(val)) { - return { - name: "var", - value: val - }; - } - const [, name] = functionRegEx.exec(val); - const value = val - .replace(new RegExp(`^${name}\\(`), "") - .replace(/\)$/, "") - .trim(); - return { - name, - value - }; - } -}; - exports.parseShorthand = function parseShorthand(val, shorthandFor, preserve = false) { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } const obj = {}; if (val === "" || exports.hasVarFunc(val)) { for (const [property] of shorthandFor) { diff --git a/test/parsers.test.js b/test/parsers.test.js index a7a8d9a2..049e75b5 100644 --- a/test/parsers.test.js +++ b/test/parsers.test.js @@ -533,7 +533,7 @@ describe("parseUrl", () => { const input = "url(sample\\\\-escaped.png)"; const output = parsers.parseUrl(input); - assert.strictEqual(output, 'url("sample\\\\-escaped.png")'); + assert.strictEqual(output, 'url("sample\\-escaped.png")'); }); it("should return undefined", () => { @@ -571,11 +571,11 @@ describe("parseUrl", () => { assert.strictEqual(output, undefined); }); - it("should return quoted url string without escape", () => { + it("should return undefined", () => { const input = "url(sample\\\nescaped\\\n-lf.png)"; const output = parsers.parseUrl(input); - assert.strictEqual(output, 'url("sample\nescaped\n-lf.png")'); + assert.strictEqual(output, undefined); }); it("should return undefined", () => { @@ -637,11 +637,11 @@ describe("parseString", () => { assert.strictEqual(output, undefined); }); - it("should return undefined", () => { + it("should return quoted string", () => { const input = "'foo bar\""; const output = parsers.parseString(input); - assert.strictEqual(output, undefined); + assert.strictEqual(output, '"foo bar\\""'); }); it("should return quoted string", () => { @@ -669,7 +669,7 @@ describe("parseString", () => { const input = '"foo \\\\ bar"'; const output = parsers.parseString(input); - assert.strictEqual(output, '"foo \\\\ bar"'); + assert.strictEqual(output, '"foo \\ bar"'); }); it("should return quoted string", () => { @@ -692,7 +692,7 @@ describe("parseColor", () => { const input = "inherit"; const output = parsers.parseColor(input); - assert.strictEqual(output, "inherit"); + assert.strictEqual(output, undefined); }); it("should return lower cased keyword for system color", () => { @@ -839,18 +839,18 @@ describe("parseImage", () => { assert.strictEqual(output, undefined); }); - it("should return none", () => { + it("should return undefined", () => { const input = "none"; const output = parsers.parseImage(input); - assert.strictEqual(output, "none"); + assert.strictEqual(output, undefined); }); - it("should return inherit", () => { + it("should return undefined", () => { const input = "inherit"; const output = parsers.parseImage(input); - assert.strictEqual(output, "inherit"); + assert.strictEqual(output, undefined); }); it("should return undefined for negative radii", () => { @@ -895,11 +895,11 @@ describe("parseImage", () => { assert.strictEqual(output, undefined); }); - it("should return undefined if value contains var() but not gradient", () => { + it("should return value even if value is not gradient but contains var()", () => { const input = "rgb(var(--my-var, 0, 0, 0))"; const output = parsers.parseImage(input); - assert.strictEqual(output, undefined); + assert.strictEqual(output, "rgb(var(--my-var, 0, 0, 0))"); }); }); From 1a3fabe2f30a0085d8f0834c8ff544f800e8f15d Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Sun, 3 Aug 2025 08:09:02 +0900 Subject: [PATCH 23/41] Add @webref/css --- package-lock.json | 16 +++++++++++--- package.json | 1 + scripts/generateImplementedProperties.mjs | 27 +++++++++++++++++++---- test/CSSStyleDeclaration.test.js | 2 +- 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3545c5ec..a844036d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@asamuzakjp/css-color": "^4.0.3", "@csstools/css-syntax-patches-for-csstree": "^1.0.14", + "@webref/css": "^6.23.6", "css-tree": "^3.1.0" }, "devDependencies": { @@ -453,9 +454,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", - "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -612,6 +613,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@webref/css": { + "version": "6.23.6", + "resolved": "https://registry.npmjs.org/@webref/css/-/css-6.23.6.tgz", + "integrity": "sha512-PdgFNBJxoYc5WTJ5yxzpoxU6drWdtiXheaYmDh1YNiQc6sFxTJbUy5bfjHxXxQ37hhsLfWxuD3DG9L4GpRahoA==", + "license": "MIT", + "peerDependencies": { + "css-tree": "^3.1.0" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", diff --git a/package.json b/package.json index dc8a3f13..640fc0a1 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@babel/traverse": "^7.26.9", "@babel/types": "^7.26.9", "@domenic/eslint-config": "^4.0.1", + "@webref/css": "^6.23.6", "eslint": "^9.22.0", "eslint-config-prettier": "^10.1.1", "eslint-plugin-prettier": "^5.2.3", diff --git a/scripts/generateImplementedProperties.mjs b/scripts/generateImplementedProperties.mjs index 6e0ceac7..efa807a0 100644 --- a/scripts/generateImplementedProperties.mjs +++ b/scripts/generateImplementedProperties.mjs @@ -1,23 +1,42 @@ import fs from "node:fs"; import path from "node:path"; +import css from "@webref/css"; import { camelCaseToDashed } from "../lib/utils/camelize.js"; +const parsedFiles = await css.listAll(); +const definitions = new Map(); +for (const { properties } of Object.values(parsedFiles)) { + if (Array.isArray(properties)) { + for (const definition of properties) { + const { name } = definition; + if (name) { + definitions.set(name, definition); + } + } + } +} + const { dirname } = import.meta; const dashedProperties = fs .readdirSync(path.resolve(dirname, "../lib/properties")) .filter((propertyFile) => path.extname(propertyFile) === ".js") - .map((propertyFile) => camelCaseToDashed(path.basename(propertyFile, ".js"))); + .map((propertyFile) => camelCaseToDashed(path.basename(propertyFile, ".js"))) + .toSorted(); + +const implementedProperties = new Map(); +for (const property of dashedProperties) { + const definition = definitions.get(property); + implementedProperties.set(property, definition); +} const outputFile = path.resolve(dirname, "../lib/generated/implementedProperties.js"); -const propertyNamesJSON = JSON.stringify(dashedProperties.toSorted(), undefined, 2); const dateToday = new Date(); const [dateTodayFormatted] = dateToday.toISOString().split("T"); const output = `"use strict"; // autogenerated - ${dateTodayFormatted} -// https://www.w3.org/Style/CSS/all-properties.en.html -module.exports = new Set(${propertyNamesJSON}); +module.exports = new Map(${JSON.stringify([...implementedProperties], null, 2)}); `; fs.writeFileSync(outputFile, output); diff --git a/test/CSSStyleDeclaration.test.js b/test/CSSStyleDeclaration.test.js index 5a7e7ada..e24d6b1f 100644 --- a/test/CSSStyleDeclaration.test.js +++ b/test/CSSStyleDeclaration.test.js @@ -11,7 +11,7 @@ const camelize = require("../lib/utils/camelize"); describe("CSSStyleDeclaration", () => { const dashedProperties = [...allProperties, ...allExtraProperties]; const allowedProperties = dashedProperties.map(camelize.dashedToCamelCase); - const invalidProperties = [...implementedProperties] + const invalidProperties = [...implementedProperties.keys()] .map(camelize.dashedToCamelCase) .filter((prop) => !allowedProperties.includes(prop)); From 21bc17d024ad34b637b3071cb8396c460dc52d38 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Sun, 3 Aug 2025 08:38:37 +0900 Subject: [PATCH 24/41] Update allProperties.js --- lib/generated/allProperties.js | 40 +++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/lib/generated/allProperties.js b/lib/generated/allProperties.js index bb01163b..a7a8c6ce 100644 --- a/lib/generated/allProperties.js +++ b/lib/generated/allProperties.js @@ -1,5 +1,5 @@ "use strict"; -// autogenerated - 2025-05-14 +// autogenerated - 2025-08-02 // https://www.w3.org/Style/CSS/all-properties.en.html module.exports = new Set([ @@ -57,10 +57,12 @@ module.exports = new Set([ "border-block-color", "border-block-end", "border-block-end-color", + "border-block-end-radius", "border-block-end-style", "border-block-end-width", "border-block-start", "border-block-start-color", + "border-block-start-radius", "border-block-start-style", "border-block-start-width", "border-block-style", @@ -68,10 +70,16 @@ module.exports = new Set([ "border-bottom", "border-bottom-color", "border-bottom-left-radius", + "border-bottom-radius", "border-bottom-right-radius", "border-bottom-style", "border-bottom-width", "border-boundary", + "border-clip", + "border-clip-bottom", + "border-clip-left", + "border-clip-right", + "border-clip-top", "border-collapse", "border-color", "border-end-end-radius", @@ -86,23 +94,29 @@ module.exports = new Set([ "border-inline-color", "border-inline-end", "border-inline-end-color", + "border-inline-end-radius", "border-inline-end-style", "border-inline-end-width", "border-inline-start", "border-inline-start-color", + "border-inline-start-radius", "border-inline-start-style", "border-inline-start-width", "border-inline-style", "border-inline-width", "border-left", "border-left-color", + "border-left-radius", "border-left-style", "border-left-width", + "border-limit", "border-radius", "border-right", "border-right-color", + "border-right-radius", "border-right-style", "border-right-width", + "border-shape", "border-spacing", "border-start-end-radius", "border-start-start-radius", @@ -110,6 +124,7 @@ module.exports = new Set([ "border-top", "border-top-color", "border-top-left-radius", + "border-top-radius", "border-top-right-radius", "border-top-style", "border-top-width", @@ -117,6 +132,11 @@ module.exports = new Set([ "bottom", "box-decoration-break", "box-shadow", + "box-shadow-blur", + "box-shadow-color", + "box-shadow-offset", + "box-shadow-position", + "box-shadow-spread", "box-sizing", "box-snap", "break-after", @@ -158,6 +178,23 @@ module.exports = new Set([ "content", "content-visibility", "continue", + "corner-block-end-shape", + "corner-block-start-shape", + "corner-bottom-left-shape", + "corner-bottom-right-shape", + "corner-bottom-shape", + "corner-end-end-shape", + "corner-end-start-shape", + "corner-inline-end-shape", + "corner-inline-start-shape", + "corner-left-shape", + "corner-right-shape", + "corner-shape", + "corner-start-end-shape", + "corner-start-start-shape", + "corner-top-left-shape", + "corner-top-right-shape", + "corner-top-shape", "counter-increment", "counter-reset", "counter-set", @@ -382,6 +419,7 @@ module.exports = new Set([ "overflow-wrap", "overflow-x", "overflow-y", + "overlay", "overscroll-behavior", "overscroll-behavior-block", "overscroll-behavior-inline", From 96d527a0278cec5cdf5bd3aebb0ad6bb244a2d77 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Sun, 3 Aug 2025 08:42:57 +0900 Subject: [PATCH 25/41] Update dev dependencies --- package-lock.json | 234 +++++++++++++++++++++------------------------- package.json | 18 ++-- 2 files changed, 117 insertions(+), 135 deletions(-) diff --git a/package-lock.json b/package-lock.json index a844036d..8658e7a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,21 +11,21 @@ "dependencies": { "@asamuzakjp/css-color": "^4.0.3", "@csstools/css-syntax-patches-for-csstree": "^1.0.14", - "@webref/css": "^6.23.6", "css-tree": "^3.1.0" }, "devDependencies": { - "@babel/generator": "^7.26.9", - "@babel/parser": "^7.26.9", - "@babel/traverse": "^7.26.9", - "@babel/types": "^7.26.9", + "@babel/generator": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.2", "@domenic/eslint-config": "^4.0.1", - "eslint": "^9.22.0", - "eslint-config-prettier": "^10.1.1", - "eslint-plugin-prettier": "^5.2.3", - "globals": "^16.0.0", + "@webref/css": "^6.23.6", + "eslint": "^9.32.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.3", + "globals": "^16.3.0", "npm-run-all": "^4.1.5", - "prettier": "^3.5.3", + "prettier": "^3.6.2", "resolve": "^1.22.10" }, "engines": { @@ -46,41 +46,51 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/generator": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz", - "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", + "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.28.0", + "@babel/types": "^7.28.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", "engines": { @@ -88,9 +98,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, "license": "MIT", "engines": { @@ -98,13 +108,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", - "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.26.9" + "@babel/types": "^7.28.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -114,58 +124,48 @@ } }, "node_modules/@babel/template": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", - "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz", - "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", + "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.9", - "@babel/parser": "^7.26.9", - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.9", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.0", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/types": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", - "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -431,9 +431,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", - "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.32.0.tgz", + "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==", "dev": true, "license": "MIT", "engines": { @@ -534,18 +534,14 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -558,27 +554,17 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -587,16 +573,16 @@ } }, "node_modules/@pkgr/core": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", - "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "dev": true, "license": "MIT", "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/unts" + "url": "https://opencollective.com/pkgr" } }, "node_modules/@types/estree": { @@ -617,6 +603,7 @@ "version": "6.23.6", "resolved": "https://registry.npmjs.org/@webref/css/-/css-6.23.6.tgz", "integrity": "sha512-PdgFNBJxoYc5WTJ5yxzpoxU6drWdtiXheaYmDh1YNiQc6sFxTJbUy5bfjHxXxQ37hhsLfWxuD3DG9L4GpRahoA==", + "dev": true, "license": "MIT", "peerDependencies": { "css-tree": "^3.1.0" @@ -1187,9 +1174,9 @@ } }, "node_modules/eslint": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", - "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz", + "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", "dev": true, "license": "MIT", "dependencies": { @@ -1199,8 +1186,8 @@ "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.15.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.31.0", - "@eslint/plugin-kit": "^0.3.1", + "@eslint/js": "9.32.0", + "@eslint/plugin-kit": "^0.3.4", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -1248,27 +1235,30 @@ } }, "node_modules/eslint-config-prettier": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.1.tgz", - "integrity": "sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw==", + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, "peerDependencies": { "eslint": ">=7.0.0" } }, "node_modules/eslint-plugin-prettier": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz", - "integrity": "sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==", + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.3.tgz", + "integrity": "sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==", "dev": true, "license": "MIT", "dependencies": { "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.9.1" + "synckit": "^0.11.7" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -1279,7 +1269,7 @@ "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", - "eslint-config-prettier": "*", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", "prettier": ">=3.0.0" }, "peerDependenciesMeta": { @@ -1592,9 +1582,9 @@ } }, "node_modules/globals": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.0.0.tgz", - "integrity": "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==", + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", + "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==", "dev": true, "license": "MIT", "engines": { @@ -2832,9 +2822,9 @@ } }, "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", "bin": { @@ -3359,29 +3349,21 @@ } }, "node_modules/synckit": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", - "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", "dev": true, "license": "MIT", "dependencies": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" + "@pkgr/core": "^0.2.9" }, "engines": { "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/unts" + "url": "https://opencollective.com/synckit" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index 640fc0a1..287b8383 100644 --- a/package.json +++ b/package.json @@ -42,18 +42,18 @@ "css-tree": "^3.1.0" }, "devDependencies": { - "@babel/generator": "^7.26.9", - "@babel/parser": "^7.26.9", - "@babel/traverse": "^7.26.9", - "@babel/types": "^7.26.9", + "@babel/generator": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.2", "@domenic/eslint-config": "^4.0.1", "@webref/css": "^6.23.6", - "eslint": "^9.22.0", - "eslint-config-prettier": "^10.1.1", - "eslint-plugin-prettier": "^5.2.3", - "globals": "^16.0.0", + "eslint": "^9.32.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.3", + "globals": "^16.3.0", "npm-run-all": "^4.1.5", - "prettier": "^3.5.3", + "prettier": "^3.6.2", "resolve": "^1.22.10" }, "scripts": { From 66fa6c5117533eaee119cfd05bac05283854d9e7 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Sun, 3 Aug 2025 09:11:51 +0900 Subject: [PATCH 26/41] Update parseFunction --- lib/parsers.js | 14 +++++------- test/parsers.test.js | 54 ++++++++++++++++++++++++++++++++------------ 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/lib/parsers.js b/lib/parsers.js index c9e8b527..5c84bd3a 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -195,7 +195,9 @@ exports.parseFunction = function parseFunction(val) { if (val === "") { return { name: null, - value: "" + value: "", + hasVar: false, + raw: "" }; } const obj = exports.parseCSS(val, { context: "value" }, true); @@ -206,19 +208,15 @@ exports.parseFunction = function parseFunction(val) { if (type !== "Function") { return; } - if (/^var$/.test(name) || exports.hasVarFunc(val)) { - return { - name: "var", - value: val - }; - } const value = val .replace(new RegExp(`^${name}\\(`), "") .replace(/\)$/, "") .trim(); return { name, - value + value, + hasVar: exports.hasVarFunc(val), + raw: val }; }; diff --git a/test/parsers.test.js b/test/parsers.test.js index 049e75b5..b341723a 100644 --- a/test/parsers.test.js +++ b/test/parsers.test.js @@ -917,7 +917,9 @@ describe("parseFunction", () => { assert.deepEqual(output, { name: null, - value: "" + value: "", + hasVar: false, + raw: "" }); }); @@ -928,23 +930,27 @@ describe("parseFunction", () => { assert.strictEqual(output, undefined); }); - it("should return object with name var and value as is for var()", () => { + it("should return object with hasVar: true", () => { const input = "var(--foo)"; const output = parsers.parseFunction(input); assert.deepEqual(output, { name: "var", - value: "var(--foo)" + value: "--foo", + hasVar: true, + raw: "var(--foo)" }); }); - it("should return object with name var and value as is for function containing var()", () => { + it("should return object with hasVar: true", () => { const input = "translate(var(--foo))"; const output = parsers.parseFunction(input); assert.deepEqual(output, { - name: "var", - value: "translate(var(--foo))" + name: "translate", + value: "var(--foo)", + hasVar: true, + raw: "translate(var(--foo))" }); }); @@ -954,7 +960,9 @@ describe("parseFunction", () => { assert.deepEqual(output, { name: "translate", - value: "0" + value: "0", + hasVar: false, + raw: "translate(0)" }); }); @@ -964,7 +972,9 @@ describe("parseFunction", () => { assert.deepEqual(output, { name: "translate", - value: "42px" + value: "42px", + hasVar: false, + raw: "translate(42px)" }); }); @@ -974,7 +984,9 @@ describe("parseFunction", () => { assert.deepEqual(output, { name: "translate", - value: "100px, 200px" + value: "100px, 200px", + hasVar: false, + raw: "translate( 100px, 200px )" }); }); @@ -984,7 +996,9 @@ describe("parseFunction", () => { assert.deepEqual(output, { name: "translateX", - value: "100px" + value: "100px", + hasVar: false, + raw: "translateX(100px)" }); }); @@ -994,7 +1008,9 @@ describe("parseFunction", () => { assert.deepEqual(output, { name: "translateY", - value: "100px" + value: "100px", + hasVar: false, + raw: "translateY(100px)" }); }); @@ -1004,7 +1020,9 @@ describe("parseFunction", () => { assert.deepEqual(output, { name: "translateZ", - value: "100px" + value: "100px", + hasVar: false, + raw: "translateZ(100px)" }); }); @@ -1014,7 +1032,9 @@ describe("parseFunction", () => { assert.deepEqual(output, { name: "translate3d", - value: "42px, -62px, -135px" + value: "42px, -62px, -135px", + hasVar: false, + raw: "translate3d(42px, -62px, -135px)" }); }); @@ -1024,7 +1044,9 @@ describe("parseFunction", () => { assert.deepEqual(output, { name: "drop-shadow", - value: "30px 10px 4px #4444dd" + value: "30px 10px 4px #4444dd", + hasVar: false, + raw: "drop-shadow(30px 10px 4px #4444dd)" }); }); @@ -1034,7 +1056,9 @@ describe("parseFunction", () => { assert.deepEqual(output, { name: "foo-bar-baz", - value: "qux" + value: "qux", + hasVar: false, + raw: "foo-bar-baz(qux)" }); }); }); From 79f8800719930f92ffdf2ad65f605a5c5ba5d588 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Sun, 3 Aug 2025 09:21:39 +0900 Subject: [PATCH 27/41] Update parsers.js --- lib/parsers.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/parsers.js b/lib/parsers.js index 5c84bd3a..81c87c52 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -63,13 +63,13 @@ const SYS_COLOR = Object.freeze([ // Regular expressions const CALC_FUNC_NAMES = "(?:a?(?:cos|sin|tan)|abs|atan2|calc|clamp|exp|hypot|log|max|min|mod|pow|rem|round|sign|sqrt)"; -const varRegEx = /^var\(/; -const varContainedRegEx = /(?<=[*/\s(])var\(/; const calcRegEx = new RegExp(`^${CALC_FUNC_NAMES}\\(`); const calcContainedRegEx = new RegExp(`(?<=[*/\\s(])${CALC_FUNC_NAMES}\\(`); const calcNameRegEx = new RegExp(`^${CALC_FUNC_NAMES}$`); +const varRegEx = /^var\(/; +const varContainedRegEx = /(?<=[*/\s(])var\(/; -// Patch css-tree. +// Patched css-tree. const cssTree = csstree.fork(syntaxes); // Prepare stringified value. From c096b7d459eacb16bd55b1c295c0d8103d614061 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Sun, 3 Aug 2025 09:34:27 +0900 Subject: [PATCH 28/41] Update backgroundColor.js --- lib/properties/backgroundColor.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/properties/backgroundColor.js b/lib/properties/backgroundColor.js index 25a466da..0f2914af 100644 --- a/lib/properties/backgroundColor.js +++ b/lib/properties/backgroundColor.js @@ -1,7 +1,6 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); module.exports.parse = function parse(v) { if (v === "") { @@ -14,7 +13,7 @@ module.exports.parse = function parse(v) { if (val) { return val; } - return strings.asciiLowercase(v); + return parsers.parseKeyword(v); } }; From 7829706c0e57d20917579d4b4e5967ade8399be3 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Mon, 4 Aug 2025 00:49:12 +0900 Subject: [PATCH 29/41] Add generic function to parse property value --- lib/parsers.js | 41 +++++++++++++++++++++++-- lib/utils/propertyDescriptors.js | 5 ++-- test/parsers.test.js | 51 ++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 5 deletions(-) diff --git a/lib/parsers.js b/lib/parsers.js index 81c87c52..5c5a1908 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -8,9 +8,9 @@ const { next: syntaxes } = require("@csstools/css-syntax-patches-for-csstree"); const csstree = require("css-tree"); const { asciiLowercase } = require("./utils/strings"); -// CSS global values +// CSS global keywords // @see https://drafts.csswg.org/css-cascade-5/#defaulting-keywords -const GLOBAL_VALUE = Object.freeze(["initial", "inherit", "unset", "revert", "revert-layer"]); +const GLOBAL_KEY = Object.freeze(["initial", "inherit", "unset", "revert", "revert-layer"]); // System colors // @see https://drafts.csswg.org/css-color/#css-system-colors @@ -433,7 +433,7 @@ exports.parseKeyword = function parseKeyword(val, validKeywords = []) { return; } const value = asciiLowercase(name); - if (validKeywords.includes(value) || GLOBAL_VALUE.includes(value)) { + if (validKeywords.includes(value) || GLOBAL_KEY.includes(value)) { return value; } }; @@ -517,3 +517,38 @@ exports.parseShorthand = function parseShorthand(val, shorthandFor, preserve = f } return obj; }; + +// Parse property value. +exports.parsePropertyValue = function parsePropertyValue(prop, val, globalObject) { + val = exports.prepareValue(val, globalObject); + if (val === "" || exports.hasVarFunc(val)) { + return val; + } else if (exports.hasCalcFunc(val)) { + val = exports.parseCalc(val, { + format: "specifiedValue" + }); + if (exports.isValidPropertyValue(prop, val)) { + return val; + } + return; + } else if (GLOBAL_KEY.includes(asciiLowercase(val))) { + return asciiLowercase(val); + } else if (SYS_COLOR.includes(asciiLowercase(val))) { + if (/^(?:-webkit-)?(?:[a-z][a-z\d]*-)*color$/i.test(prop)) { + return asciiLowercase(val); + } + return; + } + try { + const ast = exports.parseCSS(val, { + context: "value" + }); + const { error, matched } = cssTree.lexer.matchProperty(prop, ast); + if (error || !matched) { + return; + } + } catch { + return; + } + return val; +}; diff --git a/lib/utils/propertyDescriptors.js b/lib/utils/propertyDescriptors.js index 3cd05ca2..c9b241f8 100644 --- a/lib/utils/propertyDescriptors.js +++ b/lib/utils/propertyDescriptors.js @@ -1,11 +1,12 @@ "use strict"; -const prepareValue = require("../parsers").prepareValue; +const parsers = require("../parsers"); module.exports.getPropertyDescriptor = function getPropertyDescriptor(property) { return { set(v) { - this._setProperty(property, prepareValue(v)); + v = parsers.parsePropertyValue(property, v, this._global); + this._setProperty(property, v); }, get() { return this.getPropertyValue(property); diff --git a/test/parsers.test.js b/test/parsers.test.js index b341723a..db134e62 100644 --- a/test/parsers.test.js +++ b/test/parsers.test.js @@ -1221,6 +1221,57 @@ describe("parseCSS", () => { }); }); +describe("parsePropertyValue", () => { + it("should get undefined", () => { + const property = "color"; + const value = "foo"; + const output = parsers.parsePropertyValue(property, value); + assert.strictEqual(output, undefined); + }); + + it("should get empty string", () => { + const property = "color"; + const value = ""; + const output = parsers.parsePropertyValue(property, value); + assert.strictEqual(output, ""); + }); + + it("should get string", () => { + const property = "color"; + const value = "var(--foo)"; + const output = parsers.parsePropertyValue(property, value); + assert.strictEqual(output, "var(--foo)"); + }); + + it("should get string", () => { + const property = "background-size"; + const value = "calc(3em / 2)"; + const output = parsers.parsePropertyValue(property, value); + assert.deepEqual(output, "calc(1.5em)"); + }); + + it("should get string", () => { + const property = "color"; + const value = "initial"; + const output = parsers.parsePropertyValue(property, value); + assert.strictEqual(output, "initial"); + }); + + it("should get string", () => { + const property = "color"; + const value = "CanvasText"; + const output = parsers.parsePropertyValue(property, value); + assert.strictEqual(output, "canvastext"); + }); + + it("should get string", () => { + const property = "color"; + const value = "green"; + const output = parsers.parsePropertyValue(property, value); + assert.strictEqual(output, "green"); + }); +}); + describe("isValidPropertyValue", () => { it("should return false", () => { const input = "foo"; From fe2d356f98a25b7d07f57f44f3f9605046e720fe Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Mon, 4 Aug 2025 05:14:06 +0900 Subject: [PATCH 30/41] Add edge case test --- lib/properties/fontFamily.js | 9 +++++++++ test/properties.test.js | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/lib/properties/fontFamily.js b/lib/properties/fontFamily.js index b3e9359c..b61333e7 100644 --- a/lib/properties/fontFamily.js +++ b/lib/properties/fontFamily.js @@ -67,6 +67,15 @@ module.exports.parse = function parse(v) { valid = true; continue; } + if ( + i !== "undefined" && + /^(?:[A-Z][A-Za-z\d\\-]+(?:\s+[A-Z][A-Za-z\d\\-]+)*|-?[a-z][a-z-]+)$/.test(i) + ) { + const j = i.replaceAll("\\", "").replaceAll('"', '\\"').trim(); + font.push(`"${j}"`); + valid = true; + continue; + } if (!valid) { return; } diff --git a/test/properties.test.js b/test/properties.test.js index 243e42ca..537b2288 100644 --- a/test/properties.test.js +++ b/test/properties.test.js @@ -1746,6 +1746,14 @@ describe("font", () => { ); }); + it("font-family should set / get family values", () => { + testPropertyValue("font-family", "Times\\ New Roman, serif", '"Times New Roman", serif'); + }); + + it("font-family should set / get family values", () => { + testPropertyValue("font-family", '"Times\\ New Roman", serif', '"Times New Roman", serif'); + }); + it("font-family should set / get family values", () => { testPropertyValue( "font-family", From 1762619d87e6321bceb8935268824a60ddc5279f Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Mon, 4 Aug 2025 22:15:54 +0900 Subject: [PATCH 31/41] Update CSSStyleDeclaration.js --- lib/CSSStyleDeclaration.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/CSSStyleDeclaration.js b/lib/CSSStyleDeclaration.js index b0d4efc9..ef2ab3c8 100644 --- a/lib/CSSStyleDeclaration.js +++ b/lib/CSSStyleDeclaration.js @@ -519,9 +519,6 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { */ value(prefix, part, val, parser, positions = []) { val = prepareValue(val, this._global); - if (typeof val !== "string") { - return; - } part ||= ""; if (part) { part = `-${part}`; @@ -578,9 +575,6 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { */ value(prefix, part, val, parser, positions = []) { val = prepareValue(val, this._global); - if (typeof val !== "string") { - return; - } const property = `${prefix}-${part}`; if (isValidPropertyValue(property, val)) { val = parser(val); From 3cd3722db6d69573b5d67ee5df3ac662d09ca436 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Mon, 4 Aug 2025 22:38:37 +0900 Subject: [PATCH 32/41] Update parsePropertyValue() --- lib/CSSStyleDeclaration.js | 3 +- lib/parsers.js | 434 ++++++++++++++++++++++--------- lib/utils/propertyDescriptors.js | 4 +- test/parsers.test.js | 123 ++++++++- 4 files changed, 435 insertions(+), 129 deletions(-) diff --git a/lib/CSSStyleDeclaration.js b/lib/CSSStyleDeclaration.js index ef2ab3c8..a33fb64f 100644 --- a/lib/CSSStyleDeclaration.js +++ b/lib/CSSStyleDeclaration.js @@ -574,8 +574,9 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { * @param {Array.} positions */ value(prefix, part, val, parser, positions = []) { - val = prepareValue(val, this._global); const property = `${prefix}-${part}`; + // TODO: switch to parsePropertyValue() + val = prepareValue(val, this._global); if (isValidPropertyValue(property, val)) { val = parser(val); } else { diff --git a/lib/parsers.js b/lib/parsers.js index 5c5a1908..5e2f48b9 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -92,7 +92,7 @@ exports.prepareValue = function prepareValue(value, globalObject = globalThis) { default: { const str = value.toString(); if (typeof str === "string") { - return str; + return str.trim(); } throw new globalObject.TypeError(`Can not convert ${type} to string.`); } @@ -221,21 +221,31 @@ exports.parseFunction = function parseFunction(val) { }; exports.parseNumber = function parseNumber(val, restrictToPositive = false) { - if (typeof val !== "string") { - val = exports.prepareValue(val); - } - if (val === "" || exports.hasVarFunc(val)) { - return val; - } else if (exports.hasCalcFunc(val)) { - return exports.parseCalc(val, { - format: "specifiedValue" - }); + const items = []; + if (Array.isArray(val)) { + items.push(...val); + } else { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } + if (val === "" || exports.hasVarFunc(val)) { + return val; + } else if (exports.hasCalcFunc(val)) { + return exports.parseCalc(val, { + format: "specifiedValue" + }); + } + const obj = exports.parseCSS(val, { context: "value" }, true); + if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + return; + } + items.push(...obj.children); } - const obj = exports.parseCSS(val, { context: "value" }, true); - if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + if (!items.length) { return; } - const [{ type, value }] = obj.children; + const [item] = items; + const { type, value } = item ?? {}; if (type !== "Number") { return; } @@ -247,21 +257,31 @@ exports.parseNumber = function parseNumber(val, restrictToPositive = false) { }; exports.parseLength = function parseLength(val, restrictToPositive = false) { - if (typeof val !== "string") { - val = exports.prepareValue(val); - } - if (val === "" || exports.hasVarFunc(val)) { - return val; - } else if (exports.hasCalcFunc(val)) { - return exports.parseCalc(val, { - format: "specifiedValue" - }); + const items = []; + if (Array.isArray(val)) { + items.push(...val); + } else { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } + if (val === "" || exports.hasVarFunc(val)) { + return val; + } else if (exports.hasCalcFunc(val)) { + return exports.parseCalc(val, { + format: "specifiedValue" + }); + } + const obj = exports.parseCSS(val, { context: "value" }, true); + if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + return; + } + items.push(...obj.children); } - const obj = exports.parseCSS(val, { context: "value" }, true); - if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + if (!items.length) { return; } - const [{ type, unit, value }] = obj.children; + const [item] = items; + const { type, value, unit } = item ?? {}; if (type !== "Dimension" && type !== "Number") { return; } @@ -277,21 +297,31 @@ exports.parseLength = function parseLength(val, restrictToPositive = false) { }; exports.parsePercent = function parsePercent(val, restrictToPositive = false) { - if (typeof val !== "string") { - val = exports.prepareValue(val); - } - if (val === "" || exports.hasVarFunc(val)) { - return val; - } else if (exports.hasCalcFunc(val)) { - return exports.parseCalc(val, { - format: "specifiedValue" - }); + const items = []; + if (Array.isArray(val)) { + items.push(...val); + } else { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } + if (val === "" || exports.hasVarFunc(val)) { + return val; + } else if (exports.hasCalcFunc(val)) { + return exports.parseCalc(val, { + format: "specifiedValue" + }); + } + const obj = exports.parseCSS(val, { context: "value" }, true); + if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + return; + } + items.push(...obj.children); } - const obj = exports.parseCSS(val, { context: "value" }, true); - if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + if (!items.length) { return; } - const [{ type, value }] = obj.children; + const [item] = items; + const { type, value } = item ?? {}; if (type !== "Percentage" && type !== "Number") { return; } @@ -307,21 +337,31 @@ exports.parsePercent = function parsePercent(val, restrictToPositive = false) { // Either a length or a percent. exports.parseMeasurement = function parseMeasurement(val, restrictToPositive = false) { - if (typeof val !== "string") { - val = exports.prepareValue(val); - } - if (val === "" || exports.hasVarFunc(val)) { - return val; - } else if (exports.hasCalcFunc(val)) { - return exports.parseCalc(val, { - format: "specifiedValue" - }); + const items = []; + if (Array.isArray(val)) { + items.push(...val); + } else { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } + if (val === "" || exports.hasVarFunc(val)) { + return val; + } else if (exports.hasCalcFunc(val)) { + return exports.parseCalc(val, { + format: "specifiedValue" + }); + } + const obj = exports.parseCSS(val, { context: "value" }, true); + if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + return; + } + items.push(...obj.children); } - const obj = exports.parseCSS(val, { context: "value" }, true); - if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + if (!items.length) { return; } - const [{ type, unit, value }] = obj.children; + const [item] = items; + const { type, value, unit } = item ?? {}; if (type !== "Dimension" && type !== "Number" && type !== "Percentage") { return; } @@ -339,21 +379,31 @@ exports.parseMeasurement = function parseMeasurement(val, restrictToPositive = f }; exports.parseAngle = function parseAngle(val, normalizeDeg = false) { - if (typeof val !== "string") { - val = exports.prepareValue(val); - } - if (val === "" || exports.hasVarFunc(val)) { - return val; - } else if (exports.hasCalcFunc(val)) { - return exports.parseCalc(val, { - format: "specifiedValue" - }); + const items = []; + if (Array.isArray(val)) { + items.push(...val); + } else { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } + if (val === "" || exports.hasVarFunc(val)) { + return val; + } else if (exports.hasCalcFunc(val)) { + return exports.parseCalc(val, { + format: "specifiedValue" + }); + } + const obj = exports.parseCSS(val, { context: "value" }, true); + if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + return; + } + items.push(...obj.children); } - const obj = exports.parseCSS(val, { context: "value" }, true); - if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + if (!items.length) { return; } - const [{ type, unit, value }] = obj.children; + const [item] = items; + const { type, value, unit } = item ?? {}; if (type !== "Dimension" && type !== "Number") { return; } @@ -375,22 +425,32 @@ exports.parseAngle = function parseAngle(val, normalizeDeg = false) { }; exports.parseUrl = function parseUrl(val) { - if (typeof val !== "string") { - val = exports.prepareValue(val); - } - if (val === "") { - return val; - } - let obj; - try { - obj = exports.parseCSS(val, { context: "value" }, true); - } catch { - return; + const items = []; + if (Array.isArray(val)) { + items.push(...val); + } else { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } + if (val === "") { + return val; + } + let obj; + try { + obj = exports.parseCSS(val, { context: "value" }, true); + } catch { + return; + } + if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + return; + } + items.push(...obj.children); } - if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + if (!items.length) { return; } - const [{ type, value }] = obj.children; + const [item] = items; + const { type, value } = item ?? {}; if (type !== "Url") { return; } @@ -399,17 +459,32 @@ exports.parseUrl = function parseUrl(val) { }; exports.parseString = function parseString(val) { - if (typeof val !== "string") { - val = exports.prepareValue(val); - } - if (val === "") { - return ""; + const items = []; + if (Array.isArray(val)) { + items.push(...val); + } else { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } + if (val === "") { + return val; + } + let obj; + try { + obj = exports.parseCSS(val, { context: "value" }, true); + } catch { + return; + } + if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + return; + } + items.push(...obj.children); } - const obj = exports.parseCSS(val, { context: "value" }, true); - if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + if (!items.length) { return; } - const [{ type, value }] = obj.children; + const [item] = items; + const { type, value } = item ?? {}; if (type !== "String") { return; } @@ -418,17 +493,32 @@ exports.parseString = function parseString(val) { }; exports.parseKeyword = function parseKeyword(val, validKeywords = []) { - if (typeof val !== "string") { - val = exports.prepareValue(val); - } - if (val === "" || varRegEx.test(val)) { - return val; + const items = []; + if (Array.isArray(val)) { + items.push(...val); + } else { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } + if (val === "") { + return val; + } + let obj; + try { + obj = exports.parseCSS(val, { context: "value" }, true); + } catch { + return; + } + if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + return; + } + items.push(...obj.children); } - const obj = exports.parseCSS(val, { context: "value" }, true); - if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + if (!items.length) { return; } - const [{ name, type }] = obj.children; + const [item] = items; + const { name, type } = item ?? {}; if (type !== "Identifier" || name === "undefined") { return; } @@ -439,40 +529,101 @@ exports.parseKeyword = function parseKeyword(val, validKeywords = []) { }; exports.parseColor = function parseColor(val) { - if (typeof val !== "string") { - val = exports.prepareValue(val); + const items = []; + if (Array.isArray(val)) { + items.push(...val); + } else { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } + if (val === "" || exports.hasVarFunc(val)) { + return val; + } + if (/^[a-z]+$/i.test(val)) { + const v = asciiLowercase(val); + if (SYS_COLOR.includes(v)) { + return v; + } + } + items.push({ + type: "Function", + raw: val + }); } - if (val === "" || exports.hasVarFunc(val)) { - return val; + if (!items.length) { + return; } - if (/^[a-z]+$/i.test(val)) { - const v = asciiLowercase(val); - if (SYS_COLOR.includes(v)) { - return v; + const [item] = items; + const { name, raw, type } = item; + switch (type) { + case "Function": { + const res = resolveColor(raw, { + format: "specifiedValue" + }); + if (res) { + return res; + } + break; } - } - const res = resolveColor(val, { - format: "specifiedValue" - }); - if (res) { - return res; + case "Identifier": { + const res = resolveColor(name, { + format: "specifiedValue" + }); + if (res) { + return res; + } + break; + } + default: } }; exports.parseImage = function parseImage(val) { - if (typeof val !== "string") { - val = exports.prepareValue(val); + const items = []; + if (Array.isArray(val)) { + items.push(...val); + } else { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } + if (val === "" || exports.hasVarFunc(val)) { + return val; + } + const func = exports.parseFunction(val); + const url = exports.parseUrl(val); + if (func) { + func.type = "Function"; + items.push(func); + } else if (url) { + items.push({ + type: "Url", + value: url.replace(/^url\("/, "").replace(/"\)$/, "") + }); + } } - if (val === "" || exports.hasVarFunc(val)) { - return val; + if (!items.length) { + return; } - const res = resolveGradient(val, { - format: "specifiedValue" - }); - if (res) { - return res; + const [item] = items; + const { name, raw, type } = item ?? {}; + switch (type) { + case "Function": { + const res = resolveGradient(raw, { + format: "specifiedValue" + }); + if (res) { + return res; + } + break; + } + case "Identifier": { + return name; + } + case "Url": { + return exports.parseUrl(items); + } + default: } - return exports.parseUrl(val); }; exports.parseShorthand = function parseShorthand(val, shorthandFor, preserve = false) { @@ -518,37 +669,68 @@ exports.parseShorthand = function parseShorthand(val, shorthandFor, preserve = f return obj; }; -// Parse property value. -exports.parsePropertyValue = function parsePropertyValue(prop, val, globalObject) { +// Parse property value. Returns string or array of parsed object. +exports.parsePropertyValue = function parsePropertyValue(prop, val, opt = {}) { + const { globalObject, inArray } = opt; val = exports.prepareValue(val, globalObject); if (val === "" || exports.hasVarFunc(val)) { return val; } else if (exports.hasCalcFunc(val)) { - val = exports.parseCalc(val, { + const parsedValue = exports.parseCalc(val, { format: "specifiedValue" }); - if (exports.isValidPropertyValue(prop, val)) { - return val; + if (typeof parsedValue !== "string") { + return; } - return; - } else if (GLOBAL_KEY.includes(asciiLowercase(val))) { - return asciiLowercase(val); - } else if (SYS_COLOR.includes(asciiLowercase(val))) { + if (calcRegEx.test(parsedValue)) { + if (exports.isValidPropertyValue(prop, parsedValue)) { + return parsedValue; + } + return; + } + val = parsedValue; + } + const value = asciiLowercase(val); + if (GLOBAL_KEY.includes(value)) { + return value; + } else if (SYS_COLOR.includes(value)) { if (/^(?:-webkit-)?(?:[a-z][a-z\d]*-)*color$/i.test(prop)) { - return asciiLowercase(val); + return value; } return; } try { - const ast = exports.parseCSS(val, { + const ast = exports.parseCSS(value, { context: "value" }); const { error, matched } = cssTree.lexer.matchProperty(prop, ast); if (error || !matched) { return; } + if (inArray) { + const obj = cssTree.toPlainObject(ast); + const items = obj.children; + if (items.length === 1) { + const [{ name, type }] = items; + if (type === "Function") { + const itemValue = value + .replace(new RegExp(`^${name}\\(`), "") + .replace(/\)$/, "") + .trim(); + return [ + { + name, + type, + value: itemValue, + raw: val + } + ]; + } + } + return items; + } } catch { return; } - return val; + return value; }; diff --git a/lib/utils/propertyDescriptors.js b/lib/utils/propertyDescriptors.js index c9b241f8..918ab9dd 100644 --- a/lib/utils/propertyDescriptors.js +++ b/lib/utils/propertyDescriptors.js @@ -5,7 +5,9 @@ const parsers = require("../parsers"); module.exports.getPropertyDescriptor = function getPropertyDescriptor(property) { return { set(v) { - v = parsers.parsePropertyValue(property, v, this._global); + v = parsers.parsePropertyValue(property, v, { + globalObject: this._global + }); this._setProperty(property, v); }, get() { diff --git a/test/parsers.test.js b/test/parsers.test.js index db134e62..628d34b7 100644 --- a/test/parsers.test.js +++ b/test/parsers.test.js @@ -1229,6 +1229,13 @@ describe("parsePropertyValue", () => { assert.strictEqual(output, undefined); }); + it("should get undefined", () => { + const property = "color"; + const value = "calc(foo)"; + const output = parsers.parsePropertyValue(property, value); + assert.strictEqual(output, undefined); + }); + it("should get empty string", () => { const property = "color"; const value = ""; @@ -1243,11 +1250,25 @@ describe("parsePropertyValue", () => { assert.strictEqual(output, "var(--foo)"); }); + it("should get string", () => { + const property = "background-size"; + const value = "calc(3 / 2)"; + const output = parsers.parsePropertyValue(property, value); + assert.strictEqual(output, "calc(1.5)"); + }); + it("should get string", () => { const property = "background-size"; const value = "calc(3em / 2)"; const output = parsers.parsePropertyValue(property, value); - assert.deepEqual(output, "calc(1.5em)"); + assert.strictEqual(output, "calc(1.5em)"); + }); + + it("should get string", () => { + const property = "background-size"; + const value = "calc(10px + 20%)"; + const output = parsers.parsePropertyValue(property, value); + assert.strictEqual(output, "calc(20% + 10px)"); }); it("should get string", () => { @@ -1270,6 +1291,106 @@ describe("parsePropertyValue", () => { const output = parsers.parsePropertyValue(property, value); assert.strictEqual(output, "green"); }); + + it("should get string", () => { + const property = "color"; + const value = "color(srgb 0 calc(1 / 2) 0)"; + const output = parsers.parsePropertyValue(property, value); + assert.strictEqual(output, "color(srgb 0 calc(1/2) 0)"); + }); + + it("should get array", () => { + const property = "color"; + const value = "color(srgb 0 calc(1 / 2) 0)"; + const output = parsers.parsePropertyValue(property, value, { + inArray: true + }); + assert.deepEqual(output, [ + { + type: "Function", + name: "color", + value: "srgb 0 calc(1/2) 0", + raw: "color(srgb 0 calc(1/2) 0)" + } + ]); + }); + + it("should get array", () => { + const property = "color"; + const value = "green"; + const output = parsers.parsePropertyValue(property, value, { + inArray: true + }); + assert.deepEqual(output, [ + { + type: "Identifier", + loc: null, + name: "green" + } + ]); + }); + + it("should get array", () => { + const property = "color"; + const value = "rgb(0 128 0)"; + const output = parsers.parsePropertyValue(property, value, { + inArray: true + }); + assert.deepEqual(output, [ + { + type: "Function", + name: "rgb", + value: "0 128 0", + raw: "rgb(0 128 0)" + } + ]); + }); + + it("should get array", () => { + const property = "background-image"; + const value = "none"; + const output = parsers.parsePropertyValue(property, value, { + inArray: true + }); + assert.deepEqual(output, [ + { + type: "Identifier", + loc: null, + name: "none" + } + ]); + }); + + it("should get array", () => { + const property = "background-image"; + const value = "url(example.png)"; + const output = parsers.parsePropertyValue(property, value, { + inArray: true + }); + assert.deepEqual(output, [ + { + type: "Url", + loc: null, + value: "example.png" + } + ]); + }); + + it("should get array", () => { + const property = "background-image"; + const value = "linear-gradient(green, blue)"; + const output = parsers.parsePropertyValue(property, value, { + inArray: true + }); + assert.deepEqual(output, [ + { + type: "Function", + name: "linear-gradient", + value: "green, blue", + raw: "linear-gradient(green, blue)" + } + ]); + }); }); describe("isValidPropertyValue", () => { From fe5bee58415e95627fe15bb03e67da3be6261e7f Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Sat, 9 Aug 2025 23:42:14 +0900 Subject: [PATCH 33/41] Update parser functions --- lib/CSSStyleDeclaration.js | 90 +++--- lib/parsers.js | 219 ++++++++++---- lib/properties/background.js | 33 ++- lib/properties/backgroundAttachment.js | 40 ++- lib/properties/backgroundClip.js | 40 ++- lib/properties/backgroundColor.js | 32 ++- lib/properties/backgroundImage.js | 51 +++- lib/properties/backgroundOrigin.js | 40 ++- lib/properties/backgroundPosition.js | 237 +++++++-------- lib/properties/backgroundRepeat.js | 83 +++--- lib/properties/backgroundSize.js | 119 +++++--- lib/properties/borderBottomColor.js | 32 ++- lib/properties/borderCollapse.js | 29 +- lib/properties/borderColor.js | 25 +- lib/properties/borderLeftColor.js | 32 ++- lib/properties/borderRightColor.js | 32 ++- lib/properties/borderSpacing.js | 56 ++-- lib/properties/borderTopColor.js | 32 ++- lib/properties/bottom.js | 40 ++- lib/properties/clear.js | 29 +- lib/properties/clip.js | 61 ++-- lib/properties/color.js | 32 ++- lib/properties/display.js | 335 +++++++++++----------- lib/properties/flex.js | 140 ++++++--- lib/properties/flexBasis.js | 35 ++- lib/properties/flexGrow.js | 31 +- lib/properties/flexShrink.js | 31 +- lib/properties/float.js | 29 +- lib/properties/floodColor.js | 32 ++- lib/properties/height.js | 40 ++- lib/properties/left.js | 40 ++- lib/properties/lightingColor.js | 32 ++- lib/properties/margin.js | 33 ++- lib/properties/marginBottom.js | 33 ++- lib/properties/marginLeft.js | 33 ++- lib/properties/marginRight.js | 33 ++- lib/properties/marginTop.js | 33 ++- lib/properties/opacity.js | 40 ++- lib/properties/outlineColor.js | 32 ++- lib/properties/padding.js | 34 ++- lib/properties/paddingBottom.js | 34 ++- lib/properties/paddingLeft.js | 34 ++- lib/properties/paddingRight.js | 34 ++- lib/properties/paddingTop.js | 34 ++- lib/properties/right.js | 40 ++- lib/properties/stopColor.js | 33 ++- lib/properties/top.js | 40 ++- lib/properties/webkitBorderAfterColor.js | 33 ++- lib/properties/webkitBorderBeforeColor.js | 33 ++- lib/properties/webkitBorderEndColor.js | 33 ++- lib/properties/webkitBorderStartColor.js | 33 ++- lib/properties/webkitColumnRuleColor.js | 33 ++- lib/properties/webkitTapHighlightColor.js | 33 ++- lib/properties/webkitTextEmphasisColor.js | 33 ++- lib/properties/webkitTextFillColor.js | 33 ++- lib/properties/webkitTextStrokeColor.js | 33 ++- lib/properties/width.js | 40 ++- test/parsers.test.js | 316 ++++++++++++++++++-- test/properties.test.js | 94 ++++++ 59 files changed, 2420 insertions(+), 976 deletions(-) diff --git a/lib/CSSStyleDeclaration.js b/lib/CSSStyleDeclaration.js index a33fb64f..b5225c6d 100644 --- a/lib/CSSStyleDeclaration.js +++ b/lib/CSSStyleDeclaration.js @@ -12,6 +12,7 @@ const { hasVarFunc, isValidPropertyValue, parseCSS, + parsePropertyValue, parseShorthand, prepareValue, splitValue @@ -252,8 +253,8 @@ class CSSStyleDeclaration { const msg = "1 argument required, but only 0 present."; throw new this._global.TypeError(msg); } - let [index] = args; - index = parseInt(index); + const [value] = args; + const index = parseInt(value); if (Number.isNaN(index) || index < 0 || index >= this._length) { return ""; } @@ -286,28 +287,28 @@ class CSSStyleDeclaration { } /** - * @param {string} property - * @param {string} value + * @param {string} prop + * @param {string} val * @param {string?} [priority] - "important" or null */ - setProperty(property, value, priority = null) { + setProperty(prop, val, priority = null) { if (this._readonly) { - const msg = `Property ${property} can not be modified.`; + const msg = `Property ${prop} can not be modified.`; const name = "NoModificationAllowedError"; throw new this._global.DOMException(msg, name); } - value = prepareValue(value, this._global); + const value = prepareValue(val, this._global); if (value === "") { - this[property] = ""; - this.removeProperty(property); + this[prop] = ""; + this.removeProperty(prop); return; } - const isCustomProperty = property.startsWith("--"); + const isCustomProperty = prop.startsWith("--"); if (isCustomProperty) { - this._setProperty(property, value, priority); + this._setProperty(prop, value, priority); return; } - property = asciiLowercase(property); + const property = asciiLowercase(prop); if (!allProperties.has(property) && !allExtraProperties.has(property)) { return; } @@ -448,8 +449,9 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { * @param {object} shorthandFor */ value(property, val, shorthandFor) { - val = prepareValue(val, this._global); - const obj = parseShorthand(val, shorthandFor); + const obj = parseShorthand(val, shorthandFor, { + globalObject: this._global + }); if (!obj) { return; } @@ -496,7 +498,6 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { * @param {Array.} positions */ value(property, val, shorthandFor, positions = []) { - val = prepareValue(val, this._global); const obj = this._shorthandSetter(property, val, shorthandFor); if (!obj) { return; @@ -518,44 +519,50 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { * @param {Array.} positions */ value(prefix, part, val, parser, positions = []) { - val = prepareValue(val, this._global); - part ||= ""; - if (part) { - part = `-${part}`; - } - const shorthandProp = `${prefix}${part}`; - let parts = []; + const suffix = part ? `-${part}` : ""; + const shorthandProp = `${prefix}${suffix}`; + const parts = []; if (val === "") { parts.push(val); - } else if (isValidPropertyValue(shorthandProp, val)) { + } else { + const value = parsePropertyValue(shorthandProp, val, { + globalObject: this._global + }); + if (typeof value !== "string") { + return; + } parts.push(...splitValue(val)); } if (!parts.length || parts.length > positions.length) { return; } - parts = parts.map((p) => parser(p)); - this._setProperty(shorthandProp, parts.join(" ")); + const properties = parts.map((p) => + parser(p, { + globalObject: this._global + }) + ); + this._setProperty(shorthandProp, properties.join(" ")); switch (positions.length) { case 4: - if (parts.length === 1) { - parts.push(parts[0], parts[0], parts[0]); - } else if (parts.length === 2) { - parts.push(parts[0], parts[1]); - } else if (parts.length === 3) { - parts.push(parts[1]); + if (properties.length === 1) { + properties.push(properties[0], properties[0], properties[0]); + } else if (properties.length === 2) { + properties.push(properties[0], properties[1]); + } else if (properties.length === 3) { + properties.push(properties[1]); } break; case 2: - if (parts.length === 1) { - parts.push(parts[0]); + if (properties.length === 1) { + properties.push(properties[0]); } break; default: } for (let i = 0; i < positions.length; i++) { - const property = `${prefix}-${positions[i]}${part}`; + const property = `${prefix}-${positions[i]}${suffix}`; this.removeProperty(property); - this._values.set(property, parts[i]); + this._values.set(property, properties[i]); } }, enumerable: false @@ -574,15 +581,14 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { * @param {Array.} positions */ value(prefix, part, val, parser, positions = []) { - const property = `${prefix}-${part}`; - // TODO: switch to parsePropertyValue() - val = prepareValue(val, this._global); - if (isValidPropertyValue(property, val)) { - val = parser(val); - } else { + const value = parser(val, { + globalObject: this._global + }); + if (typeof value !== "string") { return; } - this._setProperty(property, val); + const property = `${prefix}-${part}`; + this._setProperty(property, value); const combinedPriority = this.getPropertyPriority(prefix); const subparts = []; for (const position of positions) { diff --git a/lib/parsers.js b/lib/parsers.js index 5e2f48b9..0b64d7e4 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -99,6 +99,11 @@ exports.prepareValue = function prepareValue(value, globalObject = globalThis) { } }; +// Value is a global keyword. +exports.isGlobalKeyword = function isGlobalKeyword(val) { + return GLOBAL_KEY.includes(asciiLowercase(val)); +}; + // Value starts with and/or contains CSS var() function. exports.hasVarFunc = function hasVarFunc(val) { return varRegEx.test(val) || varContainedRegEx.test(val); @@ -220,7 +225,10 @@ exports.parseFunction = function parseFunction(val) { }; }; -exports.parseNumber = function parseNumber(val, restrictToPositive = false) { +exports.parseNumber = function parseNumber(val, opt = {}) { + const { clamp } = opt; + const max = opt.max ?? Number.INFINITY; + const min = opt.min ?? Number.NEGATIVE_INFINITY; const items = []; if (Array.isArray(val)) { items.push(...val); @@ -249,14 +257,23 @@ exports.parseNumber = function parseNumber(val, restrictToPositive = false) { if (type !== "Number") { return; } - const num = parseFloat(value); - if (restrictToPositive && num < 0) { + let num = parseFloat(value); + if (clamp) { + if (num > max) { + num = max; + } else if (num < min) { + num = min; + } + } else if (num > max || num < min) { return; } return `${num}`; }; -exports.parseLength = function parseLength(val, restrictToPositive = false) { +exports.parseLength = function parseLength(val, opt = {}) { + const { clamp } = opt; + const max = opt.max ?? Number.INFINITY; + const min = opt.min ?? Number.NEGATIVE_INFINITY; const items = []; if (Array.isArray(val)) { items.push(...val); @@ -282,11 +299,17 @@ exports.parseLength = function parseLength(val, restrictToPositive = false) { } const [item] = items; const { type, value, unit } = item ?? {}; - if (type !== "Dimension" && type !== "Number") { + if (type !== "Dimension" && !(type === "Number" && value === "0")) { return; } - const num = parseFloat(value); - if (restrictToPositive && num < 0) { + let num = parseFloat(value); + if (clamp) { + if (num > max) { + num = max; + } else if (num < min) { + num = min; + } + } else if (num > max || num < min) { return; } if (num === 0 && !unit) { @@ -296,7 +319,10 @@ exports.parseLength = function parseLength(val, restrictToPositive = false) { } }; -exports.parsePercent = function parsePercent(val, restrictToPositive = false) { +exports.parsePercent = function parsePercent(val, opt = {}) { + const { clamp } = opt; + const max = opt.max ?? Number.INFINITY; + const min = opt.min ?? Number.NEGATIVE_INFINITY; const items = []; if (Array.isArray(val)) { items.push(...val); @@ -322,11 +348,17 @@ exports.parsePercent = function parsePercent(val, restrictToPositive = false) { } const [item] = items; const { type, value } = item ?? {}; - if (type !== "Percentage" && type !== "Number") { + if (type !== "Percentage" && !(type === "Number" && value === "0")) { return; } - const num = parseFloat(value); - if (restrictToPositive && num < 0) { + let num = parseFloat(value); + if (clamp) { + if (num > max) { + num = max; + } else if (num < min) { + num = min; + } + } else if (num > max || num < min) { return; } if (num === 0) { @@ -336,7 +368,10 @@ exports.parsePercent = function parsePercent(val, restrictToPositive = false) { }; // Either a length or a percent. -exports.parseMeasurement = function parseMeasurement(val, restrictToPositive = false) { +exports.parseMeasurement = function parseMeasurement(val, opt = {}) { + const { clamp } = opt; + const max = opt.max ?? Number.INFINITY; + const min = opt.min ?? Number.NEGATIVE_INFINITY; const items = []; if (Array.isArray(val)) { items.push(...val); @@ -362,11 +397,17 @@ exports.parseMeasurement = function parseMeasurement(val, restrictToPositive = f } const [item] = items; const { type, value, unit } = item ?? {}; - if (type !== "Dimension" && type !== "Number" && type !== "Percentage") { + if (type !== "Dimension" && type !== "Percentage" && !(type === "Number" && value === "0")) { return; } - const num = parseFloat(value); - if (restrictToPositive && num < 0) { + let num = parseFloat(value); + if (clamp) { + if (num > max) { + num = max; + } else if (num < min) { + num = min; + } + } else if (num > max || num < min) { return; } if (unit) { @@ -378,7 +419,7 @@ exports.parseMeasurement = function parseMeasurement(val, restrictToPositive = f } }; -exports.parseAngle = function parseAngle(val, normalizeDeg = false) { +exports.parseAngle = function parseAngle(val) { const items = []; if (Array.isArray(val)) { items.push(...val); @@ -404,21 +445,12 @@ exports.parseAngle = function parseAngle(val, normalizeDeg = false) { } const [item] = items; const { type, value, unit } = item ?? {}; - if (type !== "Dimension" && type !== "Number") { + if (type !== "Dimension" && !(type === "Number" && value === "0")) { return; } - let num = parseFloat(value); + const num = parseFloat(value); if (unit) { - const angle = asciiLowercase(unit); - if (angle === "deg") { - if (normalizeDeg && num < 0) { - while (num < 0) { - num += 360; - } - } - num %= 360; - } - return `${num}${angle}`; + return `${num}${asciiLowercase(unit)}`; } else if (num === 0) { return `${num}deg`; } @@ -554,7 +586,7 @@ exports.parseColor = function parseColor(val) { return; } const [item] = items; - const { name, raw, type } = item; + const { name, raw, type, value } = item; switch (type) { case "Function": { const res = resolveColor(raw, { @@ -565,6 +597,15 @@ exports.parseColor = function parseColor(val) { } break; } + case "Hash": { + const res = resolveColor(`#${value}`, { + format: "specifiedValue" + }); + if (res) { + return res; + } + break; + } case "Identifier": { const res = resolveColor(name, { format: "specifiedValue" @@ -626,9 +667,10 @@ exports.parseImage = function parseImage(val) { } }; -exports.parseShorthand = function parseShorthand(val, shorthandFor, preserve = false) { +exports.parseShorthand = function parseShorthand(val, shorthandFor, opt = {}) { + const { globalObject, preserve } = opt; if (typeof val !== "string") { - val = exports.prepareValue(val); + val = exports.prepareValue(val, globalObject); } const obj = {}; if (val === "" || exports.hasVarFunc(val)) { @@ -637,27 +679,28 @@ exports.parseShorthand = function parseShorthand(val, shorthandFor, preserve = f } return obj; } - const key = exports.parseKeyword(val); - if (key) { - if (key === "inherit") { - return obj; + const value = asciiLowercase(val); + if (GLOBAL_KEY.includes(value)) { + for (const [property] of shorthandFor) { + obj[property] = value; } - return; + return obj; } - const parts = splitValue(val); - const shorthandArr = [...shorthandFor]; + const parts = splitValue(value); + const longhands = [...shorthandFor]; for (let part of parts) { if (exports.hasCalcFunc(part)) { - part = exports.parseCalc(part); + const parsedValue = exports.parseCalc(part); + part = parsedValue; } let partValid = false; - for (let i = 0; i < shorthandArr.length; i++) { - const [property, value] = shorthandArr[i]; + for (let i = 0; i < longhands.length; i++) { + const [property, longhand] = longhands[i]; if (exports.isValidPropertyValue(property, part)) { partValid = true; - obj[property] = value.parse(part); + obj[property] = longhand.parse(part, opt); if (!preserve) { - shorthandArr.splice(i, 1); + longhands.splice(i, 1); break; } } @@ -682,16 +725,18 @@ exports.parsePropertyValue = function parsePropertyValue(prop, val, opt = {}) { if (typeof parsedValue !== "string") { return; } - if (calcRegEx.test(parsedValue)) { - if (exports.isValidPropertyValue(prop, parsedValue)) { - return parsedValue; - } - return; - } val = parsedValue; } const value = asciiLowercase(val); if (GLOBAL_KEY.includes(value)) { + if (inArray) { + return [ + { + type: "GlobalKeyword", + name: value + } + ]; + } return value; } else if (SYS_COLOR.includes(value)) { if (/^(?:-webkit-)?(?:[a-z][a-z\d]*-)*color$/i.test(prop)) { @@ -711,21 +756,93 @@ exports.parsePropertyValue = function parsePropertyValue(prop, val, opt = {}) { const obj = cssTree.toPlainObject(ast); const items = obj.children; if (items.length === 1) { - const [{ name, type }] = items; + const [{ children, name, type }] = items; if (type === "Function") { const itemValue = value .replace(new RegExp(`^${name}\\(`), "") .replace(/\)$/, "") .trim(); + if (name === "calc") { + if (children.length === 1) { + const [child] = children; + if (child.type === "Number") { + return [ + { + type: "Calc", + name: "calc", + isNumber: true, + value: `${parseFloat(child.value)}`, + raw: val + } + ]; + } + } + return [ + { + type: "Calc", + name: "calc", + isNumber: false, + value: itemValue, + raw: val + } + ]; + } return [ { - name, type, + name, value: itemValue, raw: val } ]; } + } else if (items.length > 1) { + const arr = []; + for (const item of items) { + const { children, name, type } = item; + if (type === "Function") { + const raw = cssTree + .generate(item) + .replace(/\)(?!\)|\s|,)/g, ") ") + .trim(); + const itemValue = raw + .replace(new RegExp(`^${name}\\(`), "") + .replace(/\)$/, "") + .trim(); + if (name === "calc") { + if (children.length === 1) { + const [child] = children; + if (child.type === "Number") { + arr.push({ + type: "Calc", + name: "calc", + isNumber: true, + value: `${parseFloat(child.value)}`, + raw + }); + } + } else { + arr.push({ + type: "Calc", + name: "calc", + isNumber: false, + value: itemValue, + raw + }); + } + } else { + arr.push({ + type, + name, + raw, + value: itemValue + }); + } + } else { + arr.push(item); + } + } + return arr; } return items; } diff --git a/lib/properties/background.js b/lib/properties/background.js index 35233600..2efb7d6d 100644 --- a/lib/properties/background.js +++ b/lib/properties/background.js @@ -32,7 +32,8 @@ module.exports.shorthandFor = new Map([ ["background-color", backgroundColor] ]); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; } else if (parsers.hasCalcFunc(v)) { @@ -88,7 +89,7 @@ module.exports.parse = function parse(v) { switch (property) { case "background-clip": case "background-origin": { - const parsedValue = value.parse(part); + const parsedValue = value.parse(part, { globalObject }); if (parsedValue) { bgBox.push(parsedValue); } @@ -98,21 +99,21 @@ module.exports.parse = function parse(v) { if (i !== values.length - 1) { return; } - const parsedValue = value.parse(part); + const parsedValue = value.parse(part, { globalObject }); if (parsedValue) { bg[property] = parsedValue; } break; } case "background-position": { - const parsedValue = value.parse(part); + const parsedValue = value.parse(part, { globalObject }); if (parsedValue) { bgPosition.push(parsedValue); } break; } case "background-repeat": { - const parsedValue = value.parse(part); + const parsedValue = value.parse(part, { globalObject }); if (parsedValue) { bgRepeat.push(parsedValue); } @@ -122,7 +123,7 @@ module.exports.parse = function parse(v) { break; } default: { - const parsedValue = value.parse(part); + const parsedValue = value.parse(part, { globalObject }); if (parsedValue) { bg[property] = parsedValue; } @@ -144,7 +145,7 @@ module.exports.parse = function parse(v) { switch (property) { case "background-clip": case "background-origin": { - const parsedValue = value.parse(part); + const parsedValue = value.parse(part, { globalObject }); if (parsedValue) { bgBox.push(parsedValue); } @@ -154,7 +155,7 @@ module.exports.parse = function parse(v) { if (i !== l - 1) { return; } - const parsedValue = value.parse(part); + const parsedValue = value.parse(part, { globalObject }); if (parsedValue) { bg[property] = parsedValue; } @@ -164,21 +165,21 @@ module.exports.parse = function parse(v) { break; } case "background-repeat": { - const parsedValue = value.parse(part); + const parsedValue = value.parse(part, { globalObject }); if (parsedValue) { bgRepeat.push(parsedValue); } break; } case "background-size": { - const parsedValue = value.parse(part); + const parsedValue = value.parse(part, { globalObject }); if (parsedValue) { bgSize.push(parsedValue); } break; } default: { - const parsedValue = value.parse(part); + const parsedValue = value.parse(part, { globalObject }); if (parsedValue) { bg[property] = parsedValue; } @@ -193,21 +194,21 @@ module.exports.parse = function parse(v) { } if (bgPosition.length) { const { parse: parser } = module.exports.shorthandFor.get("background-position"); - const value = parser(bgPosition.join(" ")); + const value = parser(bgPosition.join(" "), { globalObject }); if (value) { bg["background-position"] = value; } } if (bgSize.length) { const { parse: parser } = module.exports.shorthandFor.get("background-size"); - const value = parser(bgSize.join(" ")); + const value = parser(bgSize.join(" "), { globalObject }); if (value) { bg["background-size"] = value; } } if (bgRepeat.length) { const { parse: parser } = module.exports.shorthandFor.get("background-repeat"); - const value = parser(bgRepeat.join(" ")); + const value = parser(bgRepeat.join(" "), { globalObject }); if (value) { bg["background-repeat"] = value; } @@ -245,7 +246,9 @@ module.exports.definition = { } this._setProperty("background", v); } else { - const bgValues = module.exports.parse(v); + const bgValues = module.exports.parse(v, { + globalObject: this._global + }); if (!Array.isArray(bgValues)) { return; } diff --git a/lib/properties/backgroundAttachment.js b/lib/properties/backgroundAttachment.js index acfcd2a7..e732b9d8 100644 --- a/lib/properties/backgroundAttachment.js +++ b/lib/properties/backgroundAttachment.js @@ -1,14 +1,37 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.isValidPropertyValue("background-attachment", v)) { - const val = strings.asciiLowercase(v); - return parsers.splitValue(val, { delimiter: "," }).join(", "); + } + const values = parsers.splitValue(v, { delimiter: "," }); + const parsedValues = []; + for (const val of values) { + const value = parsers.parsePropertyValue("background-attachment", val, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": + case "Identifier": { + parsedValues.push(name); + break; + } + default: { + return; + } + } + } else if (typeof value === "string") { + parsedValues.push(value); + } + } + if (parsedValues.length) { + return parsedValues.join(", "); } }; @@ -19,7 +42,12 @@ module.exports.definition = { this._setProperty("background", ""); this._setProperty("background-attachment", v); } else { - this._setProperty("background-attachment", module.exports.parse(v)); + this._setProperty( + "background-attachment", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/backgroundClip.js b/lib/properties/backgroundClip.js index bb132f2f..54a945ba 100644 --- a/lib/properties/backgroundClip.js +++ b/lib/properties/backgroundClip.js @@ -1,14 +1,37 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.isValidPropertyValue("background-clip", v)) { - const val = strings.asciiLowercase(v); - return parsers.splitValue(val, { delimiter: "," }).join(", "); + } + const values = parsers.splitValue(v, { delimiter: "," }); + const parsedValues = []; + for (const val of values) { + const value = parsers.parsePropertyValue("background-clip", val, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": + case "Identifier": { + parsedValues.push(name); + break; + } + default: { + return; + } + } + } else if (typeof value === "string") { + parsedValues.push(value); + } + } + if (parsedValues.length) { + return parsedValues.join(", "); } }; @@ -19,7 +42,12 @@ module.exports.definition = { this._setProperty("background", ""); this._setProperty("background-clip", v); } else { - this._setProperty("background-clip", module.exports.parse(v)); + this._setProperty( + "background-clip", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/backgroundColor.js b/lib/properties/backgroundColor.js index 0f2914af..36ecf172 100644 --- a/lib/properties/backgroundColor.js +++ b/lib/properties/backgroundColor.js @@ -2,18 +2,27 @@ const parsers = require("../parsers"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("background-color", v)) { - const val = parsers.parseColor(v); - if (val) { - return val; + const value = parsers.parsePropertyValue("background-color", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseColor(value); + } } - return parsers.parseKeyword(v); + } else if (typeof value === "string") { + return value; } }; @@ -24,7 +33,12 @@ module.exports.definition = { this._setProperty("background", ""); this._setProperty("background-color", v); } else { - this._setProperty("background-color", module.exports.parse(v)); + this._setProperty( + "background-color", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/backgroundImage.js b/lib/properties/backgroundImage.js index 9f440a17..a023d1f4 100644 --- a/lib/properties/backgroundImage.js +++ b/lib/properties/backgroundImage.js @@ -1,23 +1,43 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("background-image", v)) { - const val = parsers - .splitValue(v, { delimiter: "," }) - .map((value) => parsers.parseImage(value)) - .join(", "); - if (val) { - return val; + const values = parsers.splitValue(v, { delimiter: "," }); + const parsedValues = []; + for (const val of values) { + const value = parsers.parsePropertyValue("background-image", val, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": + case "Identifier": { + parsedValues.push(name); + break; + } + default: { + const parsedValue = parsers.parseImage(value); + if (!parsedValue) { + return; + } + parsedValues.push(parsedValue); + } + } + } else if (typeof value === "string") { + parsedValues.push(value); + } else { + return; } - return strings.asciiLowercase(v); + } + if (parsedValues.length) { + return parsedValues.join(", "); } }; @@ -28,7 +48,12 @@ module.exports.definition = { this._setProperty("background", ""); this._setProperty("background-image", v); } else { - this._setProperty("background-image", module.exports.parse(v)); + this._setProperty( + "background-image", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/backgroundOrigin.js b/lib/properties/backgroundOrigin.js index 74af911d..d2021fd6 100644 --- a/lib/properties/backgroundOrigin.js +++ b/lib/properties/backgroundOrigin.js @@ -1,14 +1,37 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.isValidPropertyValue("background-origin", v)) { - const val = strings.asciiLowercase(v); - return parsers.splitValue(val, { delimiter: "," }).join(", "); + } + const values = parsers.splitValue(v, { delimiter: "," }); + const parsedValues = []; + for (const val of values) { + const value = parsers.parsePropertyValue("background-origin", val, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": + case "Identifier": { + parsedValues.push(name); + break; + } + default: { + return; + } + } + } else if (typeof value === "string") { + parsedValues.push(value); + } + } + if (parsedValues.length) { + return parsedValues.join(", "); } }; @@ -19,7 +42,12 @@ module.exports.definition = { this._setProperty("background", ""); this._setProperty("background-origin", v); } else { - this._setProperty("background-origin", module.exports.parse(v)); + this._setProperty( + "background-origin", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/backgroundPosition.js b/lib/properties/backgroundPosition.js index 33579e8f..9dd6ed8c 100644 --- a/lib/properties/backgroundPosition.js +++ b/lib/properties/backgroundPosition.js @@ -8,148 +8,151 @@ const keywordsX = ["center", ...keyX]; const keywordsY = ["center", ...keyY]; const keywords = ["center", ...keyX, ...keyY]; -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); - } - if (!parsers.isValidPropertyValue("background-position", v)) { - return; } const values = parsers.splitValue(v, { delimiter: "," }); const parsedValues = []; - for (const value of values) { - const parts = parsers.splitValue(value); - if (!parts.length || parts.length > 4) { - return; - } - let parsedValue = ""; - switch (parts.length) { - case 1: { - const [part] = parts; - const val = parsers.parseMeasurement(part) || parsers.parseKeyword(part, keywords); - if (val) { - if (val === "center") { - parsedValue = `${val} ${val}`; - } else if (val === "top" || val === "bottom") { - parsedValue = `center ${val}`; - } else { - parsedValue = `${val} center`; + for (const val of values) { + const value = parsers.parsePropertyValue("background-position", val, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length) { + let parsedValue = ""; + switch (value.length) { + case 1: { + const [part1] = value; + const val1 = part1.type === "Identifier" ? part1.name : parsers.parseMeasurement([part1]); + if (val1) { + if (val1 === "center") { + parsedValue = `${val1} ${val1}`; + } else if (val1 === "top" || val1 === "bottom") { + parsedValue = `center ${val1}`; + } else { + parsedValue = `${val1} center`; + } } + break; } - break; - } - case 2: { - const [part1, part2] = parts; - const val1 = parsers.parseMeasurement(part1) || parsers.parseKeyword(part1, keywords); - const val2 = parsers.parseMeasurement(part2) || parsers.parseKeyword(part2, keywords); - if (val1 && val2) { - if (keywordsY.includes(val1) && keywordsX.includes(val2)) { - parsedValue = `${val2} ${val1}`; - } else if (keywordsX.includes(val1)) { - if (val2 === "center" || !keywordsX.includes(val2)) { + case 2: { + const [part1, part2] = value; + const val1 = part1.type === "Identifier" ? part1.name : parsers.parseMeasurement([part1]); + const val2 = part2.type === "Identifier" ? part2.name : parsers.parseMeasurement([part2]); + if (val1 && val2) { + if (keywordsX.includes(val1) && keywordsY.includes(val2)) { parsedValue = `${val1} ${val2}`; - } - } else if (keywordsY.includes(val2)) { - if (!keywordsY.includes(val1)) { + } else if (keywordsY.includes(val1) && keywordsX.includes(val2)) { + parsedValue = `${val2} ${val1}`; + } else if (keywordsX.includes(val1)) { + if (val2 === "center" || !keywordsX.includes(val2)) { + parsedValue = `${val1} ${val2}`; + } + } else if (keywordsY.includes(val2)) { + if (!keywordsY.includes(val1)) { + parsedValue = `${val1} ${val2}`; + } + } else if (!keywordsY.includes(val1) && !keywordsX.includes(val2)) { parsedValue = `${val1} ${val2}`; } - } else if (!keywordsY.includes(val1) && !keywordsX.includes(val2)) { - parsedValue = `${val1} ${val2}`; } + break; } - break; - } - case 3: { - const [part1, part2, part3] = parts; - const val1 = parsers.parseKeyword(part1, keywords); - const val2 = parsers.parseMeasurement(part2) || parsers.parseKeyword(part2, keywords); - const val3 = parsers.parseMeasurement(part3) || parsers.parseKeyword(part3, keywords); - if (val1 && val2 && val3) { - let posX = ""; - let offX = ""; - let posY = ""; - let offY = ""; - if (keywordsX.includes(val1)) { - if (keyY.includes(val2)) { - if (!keywords.includes(val3)) { - posX = val1; - posY = val2; - offY = val3; + case 3: { + const [part1, part2, part3] = value; + const val1 = part1.type === "Identifier" && part1.name; + const val2 = part2.type === "Identifier" ? part2.name : parsers.parseMeasurement([part2]); + const val3 = part3.type === "Identifier" ? part3.name : parsers.parseMeasurement([part3]); + if (val1 && val2 && val3) { + let posX = ""; + let offX = ""; + let posY = ""; + let offY = ""; + if (keywordsX.includes(val1)) { + if (keyY.includes(val2)) { + if (!keywords.includes(val3)) { + posX = val1; + posY = val2; + offY = val3; + } + } else if (keyY.includes(val3)) { + if (!keywords.includes(val2)) { + posX = val1; + offX = val2; + posY = val3; + } } - } else if (keyY.includes(val3)) { - if (!keywords.includes(val2)) { - posX = val1; - offX = val2; - posY = val3; + } else if (keywordsY.includes(val1)) { + if (keyX.includes(val2)) { + if (!keywords.includes(val3)) { + posX = val2; + offX = val3; + posY = val1; + } + } else if (keyX.includes(val3)) { + if (!keywords.includes(val2)) { + posX = val3; + posY = val1; + offY = val2; + } } } - } else if (keywordsY.includes(val1)) { - if (keyX.includes(val2)) { - if (!keywords.includes(val3)) { - posX = val2; - offX = val3; - posY = val1; - } - } else if (keyX.includes(val3)) { - if (!keywords.includes(val2)) { - posX = val3; - posY = val1; - offY = val2; + if (posX && posY) { + if (offX) { + parsedValue = `${posX} ${offX} ${posY}`; + } else if (offY) { + parsedValue = `${posX} ${posY} ${offY}`; } } } - if (posX && posY) { - if (offX) { - parsedValue = `${posX} ${offX} ${posY}`; - } else if (offY) { - parsedValue = `${posX} ${posY} ${offY}`; + break; + } + case 4: { + const [part1, part2, part3, part4] = value; + const val1 = part1.type === "Identifier" && part1.name; + const val2 = parsers.parseMeasurement([part2]); + const val3 = part3.type === "Identifier" && part3.name; + const val4 = parsers.parseMeasurement([part4]); + if (val1 && val2 && val3 && val4) { + let posX = ""; + let offX = ""; + let posY = ""; + let offY = ""; + if (keywordsX.includes(val1) && keyY.includes(val3)) { + posX = val1; + offX = val2; + posY = val3; + offY = val4; + } else if (keyX.includes(val1) && keywordsY.includes(val3)) { + posX = val1; + offX = val2; + posY = val3; + offY = val4; + } else if (keyY.includes(val1) && keywordsX.includes(val3)) { + posX = val3; + offX = val4; + posY = val1; + offY = val2; + } + if (posX && offX && posY && offY) { + parsedValue = `${posX} ${offX} ${posY} ${offY}`; } } + break; } - break; + default: } - case 4: - default: { - const [part1, part2, part3, part4] = parts; - const val1 = parsers.parseKeyword(part1, keywords); - const val2 = parsers.parseMeasurement(part2); - const val3 = parsers.parseKeyword(part3, keywords); - const val4 = parsers.parseMeasurement(part4); - if (val1 && val2 && val3 && val4) { - let posX = ""; - let offX = ""; - let posY = ""; - let offY = ""; - if (keywordsX.includes(val1) && keyY.includes(val3)) { - posX = val1; - offX = val2; - posY = val3; - offY = val4; - } else if (keyX.includes(val1) && keywordsY.includes(val3)) { - posX = val1; - offX = val2; - posY = val3; - offY = val4; - } else if (keyY.includes(val1) && keywordsX.includes(val3)) { - posX = val3; - offX = val4; - posY = val1; - offY = val2; - } - if (posX && offX && posY && offY) { - parsedValue = `${posX} ${offX} ${posY} ${offY}`; - } - } + if (parsedValue) { + parsedValues.push(parsedValue); + } else { + return; } - } - if (parsedValue) { - parsedValues.push(parsedValue); - } else { - return; + } else if (typeof value === "string") { + parsedValues.push(value); } } if (parsedValues.length) { diff --git a/lib/properties/backgroundRepeat.js b/lib/properties/backgroundRepeat.js index ead57829..5b8a2e33 100644 --- a/lib/properties/backgroundRepeat.js +++ b/lib/properties/backgroundRepeat.js @@ -2,60 +2,57 @@ const parsers = require("../parsers"); -const keywordsAxis = ["repeat-x", "repeat-y"]; -const keywordsRepeat = ["repeat", "no-repeat", "space", "round"]; -const keywords = [...keywordsAxis, ...keywordsRepeat]; - -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); - } - if (!parsers.isValidPropertyValue("background-repeat", v)) { - return; } const values = parsers.splitValue(v, { delimiter: "," }); const parsedValues = []; - for (const value of values) { - const parts = parsers.splitValue(value); - if (!parts.length || parts.length > 2) { - return; - } - let parsedValue = ""; - switch (parts.length) { - case 1: { - const [part] = parts; - const val = parsers.parseKeyword(part, keywords); - if (val) { - parsedValue = val; - } - break; - } - case 2: - default: { - const [part1, part2] = parts; - const val1 = parsers.parseKeyword(part1, keywordsRepeat); - const val2 = parsers.parseKeyword(part2, keywordsRepeat); - if (val1 && val2) { - if (val1 === "repeat" && val2 === "no-repeat") { - parsedValue = "repeat-x"; - } else if (val1 === "no-repeat" && val2 === "repeat") { - parsedValue = "repeat-y"; - } else if (val1 === val2) { + for (const val of values) { + const value = parsers.parsePropertyValue("background-repeat", val, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length) { + let parsedValue = ""; + switch (value.length) { + case 1: { + const [part1] = value; + const val1 = part1.type === "Identifier" && part1.name; + if (val1) { parsedValue = val1; - } else { - parsedValue = `${val1} ${val2}`; } + break; } + case 2: { + const [part1, part2] = value; + const val1 = part1.type === "Identifier" && part1.name; + const val2 = part2.type === "Identifier" && part2.name; + if (val1 && val2) { + if (val1 === "repeat" && val2 === "no-repeat") { + parsedValue = "repeat-x"; + } else if (val1 === "no-repeat" && val2 === "repeat") { + parsedValue = "repeat-y"; + } else if (val1 === val2) { + parsedValue = val1; + } else { + parsedValue = `${val1} ${val2}`; + } + } + break; + } + default: } - } - if (parsedValue) { - parsedValues.push(parsedValue); - } else { - return; + if (parsedValue) { + parsedValues.push(parsedValue); + } else { + return; + } + } else if (typeof value === "string") { + parsedValues.push(value); } } if (parsedValues.length) { diff --git a/lib/properties/backgroundSize.js b/lib/properties/backgroundSize.js index 269ef301..0a1e315d 100644 --- a/lib/properties/backgroundSize.js +++ b/lib/properties/backgroundSize.js @@ -2,58 +2,88 @@ const parsers = require("../parsers"); -const keywordsRatio = ["contain", "cover"]; -const keywordsRepeat = ["auto"]; -const keywords = [...keywordsRatio, ...keywordsRepeat]; - -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); - } - if (!parsers.isValidPropertyValue("background-size", v)) { - return; } const values = parsers.splitValue(v, { delimiter: "," }); const parsedValues = []; - for (const value of values) { - const parts = parsers.splitValue(value); - if (!parts.length || parts.length > 2) { - return; - } - let parsedValue = ""; - switch (parts.length) { - case 1: { - const [part] = parts; - const val = parsers.parseMeasurement(part, true) || parsers.parseKeyword(part, keywords); - if (val) { - parsedValue = val; + for (const val of values) { + const value = parsers.parsePropertyValue("background-size", val, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length) { + if (value.length === 1) { + const [{ isNumber, name, raw, type }] = value; + switch (type) { + case "Calc": { + if (isNumber) { + return; + } + parsedValues.push(raw); + break; + } + case "GlobalKeyword": + case "Identifier": { + parsedValues.push(name); + break; + } + default: { + const parsedValue = parsers.parseMeasurement(value); + if (!parsedValue) { + return; + } + parsedValues.push(parsedValue); + } } - break; - } - case 2: - default: { - const [part1, part2] = parts; - const val1 = - parsers.parseMeasurement(part1, true) || parsers.parseKeyword(part1, keywordsRepeat); - const val2 = - parsers.parseMeasurement(part2, true) || parsers.parseKeyword(part2, keywordsRepeat); - if (val1 && val2) { - if (val2 === "auto") { - parsedValue = val1; - } else { - parsedValue = `${val1} ${val2}`; + } else { + const [val1, val2] = value; + const parts = []; + if (val1.type === "Calc" && !val1.isNumber) { + parts.push(val1.raw); + } else if (val1.type === "Identifier") { + parts.push(val1.name); + } else if (val1.type === "Dimension") { + parts.push(`${val1.value}${val1.unit}`); + } else if (val1.type === "Percentage") { + parts.push(`${val1.value}%`); + } else { + return; + } + switch (val2.type) { + case "Calc": { + if (val2.isNumber) { + return; + } + parts.push(val2.raw); + break; + } + case "Dimension": { + parts.push(`${val2.value}${val2.unit}`); + break; + } + case "Identifier": { + if (val2.name !== "auto") { + parts.push(val2.name); + } + break; + } + case "Percentage": { + parts.push(`${val2.value}%`); + break; + } + default: { + return; } } + parsedValues.push(parts.join(" ")); } - } - if (parsedValue) { - parsedValues.push(parsedValue); - } else { - return; + } else if (typeof value === "string") { + parsedValues.push(value); } } if (parsedValues.length) { @@ -68,7 +98,12 @@ module.exports.definition = { this._setProperty("background", ""); this._setProperty("background-size", v); } else { - this._setProperty("background-size", module.exports.parse(v)); + this._setProperty( + "background-size", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/borderBottomColor.js b/lib/properties/borderBottomColor.js index 8839a1ca..18039f03 100644 --- a/lib/properties/borderBottomColor.js +++ b/lib/properties/borderBottomColor.js @@ -2,18 +2,27 @@ const parsers = require("../parsers"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("border-bottom-color", v)) { - const val = parsers.parseColor(v); - if (val) { - return val; + const value = parsers.parsePropertyValue("border-bottom-color", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseColor(value); + } } - return parsers.parseKeyword(v); + } else if (typeof value === "string") { + return value; } }; @@ -26,7 +35,12 @@ module.exports.definition = { this._setProperty("border-bottom", ""); this._setProperty("border-bottom-color", v); } else { - this._setProperty("border-bottom-color", module.exports.parse(v)); + this._setProperty( + "border-bottom-color", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/borderCollapse.js b/lib/properties/borderCollapse.js index 2c92550b..e1288eb3 100644 --- a/lib/properties/borderCollapse.js +++ b/lib/properties/borderCollapse.js @@ -1,13 +1,27 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.isValidPropertyValue("border-collapse", v)) { - return strings.asciiLowercase(v); + } + const value = parsers.parsePropertyValue("border-collapse", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: + } + } else if (typeof value === "string") { + return value; } }; @@ -18,7 +32,12 @@ module.exports.definition = { this._setProperty("border", ""); this._setProperty("border-collapse", v); } else { - this._setProperty("border-collapse", module.exports.parse(v)); + this._setProperty( + "border-collapse", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/borderColor.js b/lib/properties/borderColor.js index 7583bd07..0eea31a8 100644 --- a/lib/properties/borderColor.js +++ b/lib/properties/borderColor.js @@ -2,18 +2,27 @@ const parsers = require("../parsers"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("border-color", v)) { - const val = parsers.parseColor(v); - if (val) { - return val; + const value = parsers.parsePropertyValue("border-color", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseColor(value); + } } - return parsers.parseKeyword(v); + } else if (typeof value === "string") { + return value; } }; diff --git a/lib/properties/borderLeftColor.js b/lib/properties/borderLeftColor.js index 8103dff3..a4c9768e 100644 --- a/lib/properties/borderLeftColor.js +++ b/lib/properties/borderLeftColor.js @@ -2,18 +2,27 @@ const parsers = require("../parsers"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("border-left-color", v)) { - const val = parsers.parseColor(v); - if (val) { - return val; + const value = parsers.parsePropertyValue("border-left-color", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseColor(value); + } } - return parsers.parseKeyword(v); + } else if (typeof value === "string") { + return value; } }; @@ -26,7 +35,12 @@ module.exports.definition = { this._setProperty("border-left", ""); this._setProperty("border-left-color", v); } else { - this._setProperty("border-left-color", module.exports.parse(v)); + this._setProperty( + "border-left-color", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/borderRightColor.js b/lib/properties/borderRightColor.js index bd9906c6..950e7fc5 100644 --- a/lib/properties/borderRightColor.js +++ b/lib/properties/borderRightColor.js @@ -2,18 +2,27 @@ const parsers = require("../parsers"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("border-right-color", v)) { - const val = parsers.parseColor(v); - if (val) { - return val; + const value = parsers.parsePropertyValue("border-right-color", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseColor(value); + } } - return parsers.parseKeyword(v); + } else if (typeof value === "string") { + return value; } }; @@ -26,7 +35,12 @@ module.exports.definition = { this._setProperty("border-right", ""); this._setProperty("border-right-color", v); } else { - this._setProperty("border-right-color", module.exports.parse(v)); + this._setProperty( + "border-right-color", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/borderSpacing.js b/lib/properties/borderSpacing.js index d0583a4b..acae9b5c 100644 --- a/lib/properties/borderSpacing.js +++ b/lib/properties/borderSpacing.js @@ -2,32 +2,39 @@ const parsers = require("../parsers"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (!parsers.isValidPropertyValue("border-spacing", v)) { - return; - } - const key = parsers.parseKeyword(v); - if (key) { - return key; - } - const parts = parsers.splitValue(v); - if (!parts.length || parts.length > 2) { - return; - } - const values = []; - for (const part of parts) { - const val = parsers.parseLength(part); - if (!val) { - return; + const value = parsers.parsePropertyValue("border-spacing", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length) { + switch (value.length) { + case 1: { + const [part1] = value; + const val1 = parsers.parseLength([part1]); + if (val1) { + return val1; + } + break; + } + case 2: { + const [part1, part2] = value; + const val1 = parsers.parseLength([part1]); + const val2 = parsers.parseLength([part2]); + if (val1 && val2) { + return `${val1} ${val2}`; + } + break; + } + default: } - values.push(val); + } else if (typeof value === "string") { + return value; } - return values.join(" "); }; module.exports.definition = { @@ -37,7 +44,12 @@ module.exports.definition = { this._setProperty("border", ""); this._setProperty("border-spacing", v); } else { - this._setProperty("border-spacing", module.exports.parse(v)); + this._setProperty( + "border-spacing", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/borderTopColor.js b/lib/properties/borderTopColor.js index f675d99f..b9116846 100644 --- a/lib/properties/borderTopColor.js +++ b/lib/properties/borderTopColor.js @@ -2,18 +2,27 @@ const parsers = require("../parsers"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("border-top-color", v)) { - const val = parsers.parseColor(v); - if (val) { - return val; + const value = parsers.parsePropertyValue("border-top-color", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseColor(value); + } } - return parsers.parseKeyword(v); + } else if (typeof value === "string") { + return value; } }; @@ -26,7 +35,12 @@ module.exports.definition = { this._setProperty("border-top", ""); this._setProperty("border-top-color", v); } else { - this._setProperty("border-top-color", module.exports.parse(v)); + this._setProperty( + "border-top-color", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/bottom.js b/lib/properties/bottom.js index 109cdd6f..c1dd9578 100644 --- a/lib/properties/bottom.js +++ b/lib/properties/bottom.js @@ -1,20 +1,35 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("bottom", v)) { - const val = parsers.parseMeasurement(v); - if (val) { - return val; + const value = parsers.parsePropertyValue("bottom", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, raw, type }] = value; + switch (type) { + case "Calc": { + if (!isNumber) { + return raw; + } + break; + } + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: { + return parsers.parseMeasurement(value); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; @@ -24,7 +39,12 @@ module.exports.definition = { if (parsers.hasVarFunc(v)) { this._setProperty("bottom", v); } else { - this._setProperty("bottom", module.exports.parse(v)); + this._setProperty( + "bottom", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/clear.js b/lib/properties/clear.js index 814d8d98..e033910e 100644 --- a/lib/properties/clear.js +++ b/lib/properties/clear.js @@ -1,13 +1,27 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.isValidPropertyValue("clear", v)) { - return strings.asciiLowercase(v); + } + const value = parsers.parsePropertyValue("clear", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: + } + } else if (typeof value === "string") { + return value; } }; @@ -17,7 +31,12 @@ module.exports.definition = { if (parsers.hasVarFunc(v)) { this._setProperty("clear", v); } else { - this._setProperty("clear", module.exports.parse(v)); + this._setProperty( + "clear", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/clip.js b/lib/properties/clip.js index 41c2a41b..75f09853 100644 --- a/lib/properties/clip.js +++ b/lib/properties/clip.js @@ -3,35 +3,43 @@ // @see https://drafts.fxtf.org/css-masking/#clip-property const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; } - const val = parsers.parseKeyword(v, ["auto"]); - if (val) { - return val; - } - // parse legacy - v = strings.asciiLowercase(v); - const matches = v.match(/^rect\(\s*(.*)\s*\)$/); - if (!matches) { - return; - } - const parts = matches[1].split(/\s*,\s*/); - if (parts.length !== 4) { - return; - } - const valid = parts.every(function (part, index) { - const measurement = parsers.parseMeasurement(part.trim()); - parts[index] = measurement; - return typeof measurement === "string"; + const value = parsers.parsePropertyValue("clip", v, { + globalObject, + inArray: true }); - if (!valid) { - return; + if (Array.isArray(value) && value.length === 1) { + const [{ name, type, value: itemValue }] = value; + switch (type) { + case "Function": { + const values = parsers.splitValue(itemValue, { + delimiter: "," + }); + const parsedValues = []; + for (const item of values) { + const val = parsers.parseMeasurement(item); + if (val) { + parsedValues.push(val); + } else { + return; + } + } + return `${name}(${parsedValues.join(", ")})`; + } + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: + } + } else if (typeof value === "string") { + return value; } - return `rect(${parts.join(", ")})`; }; module.exports.definition = { @@ -40,7 +48,12 @@ module.exports.definition = { if (parsers.hasVarFunc(v)) { this._setProperty("clip", v); } else { - this._setProperty("clip", module.exports.parse(v)); + this._setProperty( + "clip", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/color.js b/lib/properties/color.js index 3b581c93..3f9dc27b 100644 --- a/lib/properties/color.js +++ b/lib/properties/color.js @@ -2,18 +2,27 @@ const parsers = require("../parsers"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("color", v)) { - const val = parsers.parseColor(v); - if (val) { - return val; + const value = parsers.parsePropertyValue("color", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseColor(value); + } } - return parsers.parseKeyword(v); + } else if (typeof value === "string") { + return value; } }; @@ -23,7 +32,12 @@ module.exports.definition = { if (parsers.hasVarFunc(v)) { this._setProperty("color", v); } else { - this._setProperty("color", module.exports.parse(v)); + this._setProperty( + "color", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/display.js b/lib/properties/display.js index a97fc0be..6e2cf0b5 100644 --- a/lib/properties/display.js +++ b/lib/properties/display.js @@ -5,198 +5,180 @@ const parsers = require("../parsers"); /* keywords */ const displayOutside = ["block", "inline", "run-in"]; const displayFlow = ["flow", "flow-root"]; -const displayInside = ["table", "flex", "grid", "ruby", ...displayFlow]; -const displayListItem = ["list-item"]; -const displayInternal = [ - "table-row-group", - "table-header-group", - "table-footer-group", - "table-row", - "table-cell", - "table-column-group", - "table-column", - "table-caption", - "ruby-base", - "ruby-text", - "ruby-base-container", - "ruby-text-container" -]; -const displayBox = ["contents", "none"]; -const displayLegacy = ["inline-block", "inline-table", "inline-flex", "inline-grid"]; -const keywords = [ - ...displayOutside, - ...displayInside, - ...displayListItem, - ...displayInternal, - ...displayBox, - ...displayLegacy -]; -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; } - if (!parsers.isValidPropertyValue("display", v)) { - return; - } - const values = parsers.splitValue(v); - switch (values.length) { - case 1: { - let [v1] = values; - v1 = parsers.parseKeyword(v1, keywords); - if (v1) { - if (v1 === "flow") { - return "block"; - } - return v1; - } - break; - } - case 2: { - let [v1, v2] = values; - v1 = parsers.parseKeyword(v1, keywords); - v2 = parsers.parseKeyword(v2, keywords); - if (!v1 || !v2) { - return; - } - let outerValue = ""; - let innerValue = ""; - if (v1 === "list-item") { - outerValue = v2; - innerValue = v1; - } else if (v2 === "list-item") { - outerValue = v1; - innerValue = v2; - } else if (displayOutside.includes(v1)) { - outerValue = v1; - innerValue = v2; - } else if (displayOutside.includes(v2)) { - outerValue = v2; - innerValue = v1; - } - if (innerValue === "list-item") { - switch (outerValue) { - case "block": - case "flow": { - return innerValue; - } - case "flow-root": - case "inline": - case "run-in": { - return `${outerValue} ${innerValue}`; - } - default: - } - } else if (outerValue === "block") { - switch (innerValue) { - case "flow": { - return outerValue; - } - case "flow-root": - case "flex": - case "grid": - case "table": { - return innerValue; - } - case "ruby": { - return `${outerValue} ${innerValue}`; - } - default: - } - } else if (outerValue === "inline") { - switch (innerValue) { - case "flow": { - return outerValue; - } - case "flow-root": { - return `${outerValue}-block`; + const value = parsers.parsePropertyValue("display", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length) { + switch (value.length) { + case 1: { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": { + return name; } - case "flex": - case "grid": - case "table": { - return `${outerValue}-${innerValue}`; - } - case "ruby": { - return innerValue; + case "Identifier": { + if (name === "flow") { + return "block"; + } + return name; } default: } - } else if (outerValue === "run-in") { - switch (innerValue) { - case "flow": { - return outerValue; + break; + } + case 2: { + const [part1, part2] = value; + const val1 = part1.type === "Identifier" && part1.name; + const val2 = part2.type === "Identifier" && part2.name; + if (val1 && val2) { + let outerValue = ""; + let innerValue = ""; + if (val1 === "list-item") { + outerValue = val2; + innerValue = val1; + } else if (val2 === "list-item") { + outerValue = val1; + innerValue = val2; + } else if (displayOutside.includes(val1)) { + outerValue = val1; + innerValue = val2; + } else if (displayOutside.includes(val2)) { + outerValue = val2; + innerValue = val1; } - case "flow-root": - case "flex": - case "grid": - case "table": - case "ruby": { - return `${outerValue} ${innerValue}`; + if (innerValue === "list-item") { + switch (outerValue) { + case "block": + case "flow": { + return innerValue; + } + case "flow-root": + case "inline": + case "run-in": { + return `${outerValue} ${innerValue}`; + } + default: + } + } else if (outerValue === "block") { + switch (innerValue) { + case "flow": { + return outerValue; + } + case "flow-root": + case "flex": + case "grid": + case "table": { + return innerValue; + } + case "ruby": { + return `${outerValue} ${innerValue}`; + } + default: + } + } else if (outerValue === "inline") { + switch (innerValue) { + case "flow": { + return outerValue; + } + case "flow-root": { + return `${outerValue}-block`; + } + case "flex": + case "grid": + case "table": { + return `${outerValue}-${innerValue}`; + } + case "ruby": { + return innerValue; + } + default: + } + } else if (outerValue === "run-in") { + switch (innerValue) { + case "flow": { + return outerValue; + } + case "flow-root": + case "flex": + case "grid": + case "table": + case "ruby": { + return `${outerValue} ${innerValue}`; + } + default: + } } - default: } + break; } - break; - } - case 3: { - let [v1, v2, v3] = values; - v1 = parsers.parseKeyword(v1, keywords); - v2 = parsers.parseKeyword(v2, keywords); - v3 = parsers.parseKeyword(v3, keywords); - if (!v1 || !v2 || !v3) { - return; - } - let outerValue = ""; - let flowValue = ""; - let listItemValue = ""; - if (v1 === "list-item") { - listItemValue = v1; - if (displayFlow.includes(v2)) { - flowValue = v2; - outerValue = v3; - } else if (displayFlow.includes(v3)) { - flowValue = v3; - outerValue = v2; - } - } else if (v2 === "list-item") { - listItemValue = v2; - if (displayFlow.includes(v1)) { - flowValue = v1; - outerValue = v3; - } else if (displayFlow.includes(v3)) { - flowValue = v3; - outerValue = v1; - } - } else if (v3 === "list-item") { - listItemValue = v3; - if (displayFlow.includes(v1)) { - flowValue = v1; - outerValue = v2; - } else if (displayFlow.includes(v2)) { - flowValue = v2; - outerValue = v1; - } - } - if (outerValue && flowValue && listItemValue) { - switch (outerValue) { - case "block": { - if (flowValue === "flow") { - return listItemValue; + case 3: { + const [part1, part2, part3] = value; + const val1 = part1.type === "Identifier" && part1.name; + const val2 = part2.type === "Identifier" && part2.name; + const val3 = part3.type === "Identifier" && part3.name; + if (val1 && val2 && part3) { + let outerValue = ""; + let flowValue = ""; + let listItemValue = ""; + if (val1 === "list-item") { + listItemValue = val1; + if (displayFlow.includes(val2)) { + flowValue = val2; + outerValue = val3; + } else if (displayFlow.includes(val3)) { + flowValue = val3; + outerValue = val2; + } + } else if (val2 === "list-item") { + listItemValue = val2; + if (displayFlow.includes(val1)) { + flowValue = val1; + outerValue = val3; + } else if (displayFlow.includes(val3)) { + flowValue = val3; + outerValue = val1; + } + } else if (val3 === "list-item") { + listItemValue = val3; + if (displayFlow.includes(val1)) { + flowValue = val1; + outerValue = val2; + } else if (displayFlow.includes(val2)) { + flowValue = val2; + outerValue = val1; } - return `${flowValue} ${listItemValue}`; } - case "inline": - case "run-in": { - if (flowValue === "flow") { - return `${outerValue} ${listItemValue}`; + if (outerValue && flowValue && listItemValue) { + switch (outerValue) { + case "block": { + if (flowValue === "flow") { + return listItemValue; + } + return `${flowValue} ${listItemValue}`; + } + case "inline": + case "run-in": { + if (flowValue === "flow") { + return `${outerValue} ${listItemValue}`; + } + return `${outerValue} ${flowValue} ${listItemValue}`; + } } - return `${outerValue} ${flowValue} ${listItemValue}`; } } + break; } - break; + default: } - default: + } else if (typeof value === "string") { + return value; } }; @@ -206,7 +188,12 @@ module.exports.definition = { if (parsers.hasVarFunc(v)) { this._setProperty("display", v); } else { - this._setProperty("display", module.exports.parse(v)); + this._setProperty( + "display", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/flex.js b/lib/properties/flex.js index 6ed07569..a86710bf 100644 --- a/lib/properties/flex.js +++ b/lib/properties/flex.js @@ -5,49 +5,115 @@ const flexGrow = require("./flexGrow"); const flexShrink = require("./flexShrink"); const flexBasis = require("./flexBasis"); -const replaceKeys = new Map([["initial", "0 1 auto"]]); - module.exports.shorthandFor = new Map([ ["flex-grow", flexGrow], ["flex-shrink", flexShrink], ["flex-basis", flexBasis] ]); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); - } - if (!parsers.isValidPropertyValue("flex", v)) { - return; } - const key = parsers.parseKeyword(v, ["auto", "none"]); - if (key) { - switch (key) { - case "auto": { - return "1 1 auto"; + const value = parsers.parsePropertyValue("flex", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length) { + if (value.length === 1) { + const [{ isNumber, name, raw, type, unit, value: itemValue }] = value; + switch (type) { + case "Calc": { + if (isNumber) { + return `${raw} 1 0%`; + } + return `1 1 ${raw}`; + } + case "Dimension": { + return `1 1 ${itemValue}${unit}`; + } + case "GlobalKeyword": { + return name; + } + case "Identifier": { + if (name === "auto") { + return "1 1 auto"; + } else if (name === "none") { + return "0 0 auto"; + } + break; + } + case "Number": { + return `${itemValue} 1 0%`; + } + case "Percentage": { + return `1 1 ${itemValue}%`; + } + default: } - case "none": { - return "0 0 auto"; + } else { + const flex = { + "flex-grow": "1", + "flex-shrink": "1", + "flex-basis": "0%" + }; + const [val1, val2, val3] = value; + if (val1.type === "Calc" && val1.isNumber) { + flex["flex-grow"] = val1.raw; + } else if (val1.type === "Number") { + flex["flex-grow"] = val1.value; + } else { + return; } - default: { - return key; + if (val3) { + if (val2.type === "Calc" && val2.isNumber) { + flex["flex-shrink"] = val2.raw; + } else if (val2.type === "Number") { + flex["flex-shrink"] = val2.value; + } else { + return; + } + if (val3.type === "Calc" && !val3.isNumber) { + flex["flex-basis"] = val3.raw; + } else if (val3.type === "Dimension") { + flex["flex-basis"] = `${val3.value}${val3.unit}`; + } else if (val3.type === "Percentage") { + flex["flex-basis"] = `${val3.value}%`; + } else { + return; + } + } else { + switch (val2.type) { + case "Calc": { + if (val2.isNumber) { + flex["flex-shrink"] = val2.raw; + } else { + flex["flex-basis"] = val2.raw; + } + break; + } + case "Dimension": { + flex["flex-basis"] = `${val2.value}${val2.unit}`; + break; + } + case "Number": { + flex["flex-shrink"] = val2.value; + break; + } + case "Percentage": { + flex["flex-basis"] = `${val2.value}%`; + break; + } + default: { + return; + } + } } + return [...Object.values(flex)].join(" "); } - } - const obj = parsers.parseShorthand(v, module.exports.shorthandFor); - if (obj) { - const flex = { - "flex-grow": "1", - "flex-shrink": "1", - "flex-basis": "0%" - }; - const items = Object.entries(obj); - for (const [property, value] of items) { - flex[property] = value; - } - return [...Object.values(flex)].join(" "); + } else if (typeof value === "string") { + return value; } }; @@ -58,18 +124,16 @@ module.exports.definition = { this._shorthandSetter("flex", "", module.exports.shorthandFor); this._setProperty("flex", v); } else { - const val = module.exports.parse(v); - if (replaceKeys.has(val)) { - this._shorthandSetter("flex", replaceKeys.get(val), module.exports.shorthandFor); - this._setProperty("flex", val); - } else { - this._shorthandSetter("flex", val, module.exports.shorthandFor); - } + const val = module.exports.parse(v, { + globalObject: this._global + }); + this._shorthandSetter("flex", val, module.exports.shorthandFor); + this._setProperty("flex", val); } }, get() { let val = this.getPropertyValue("flex"); - if (parsers.hasVarFunc(val) || replaceKeys.has(val)) { + if (parsers.isGlobalKeyword(val) || parsers.hasVarFunc(val)) { return val; } val = this._shorthandGetter("flex", module.exports.shorthandFor); diff --git a/lib/properties/flexBasis.js b/lib/properties/flexBasis.js index 3b4745a0..84f51705 100644 --- a/lib/properties/flexBasis.js +++ b/lib/properties/flexBasis.js @@ -1,20 +1,35 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("flex-basis", v)) { - const val = parsers.parseMeasurement(v); - if (val) { - return val; + const value = parsers.parsePropertyValue("flex-basis", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, raw, type }] = value; + switch (type) { + case "Calc": { + if (!isNumber) { + return raw; + } + break; + } + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: { + return parsers.parseMeasurement(value); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; @@ -25,7 +40,7 @@ module.exports.definition = { this._setProperty("flex", ""); this._setProperty("flex-basis", v); } else { - this._setProperty("flex-basis", module.exports.parse(v)); + this._setProperty("flex-basis", module.exports.parse(v, { globalObject: this._global })); } }, get() { diff --git a/lib/properties/flexGrow.js b/lib/properties/flexGrow.js index 46d6ae6e..ddd334c3 100644 --- a/lib/properties/flexGrow.js +++ b/lib/properties/flexGrow.js @@ -2,14 +2,33 @@ const parsers = require("../parsers"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("flex-grow", v)) { - return parsers.parseNumber(v, true); + const value = parsers.parsePropertyValue("flex-grow", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, raw, type }] = value; + switch (type) { + case "Calc": { + if (isNumber) { + return raw; + } + break; + } + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseNumber(value); + } + } + } else if (typeof value === "string") { + return value; } }; @@ -20,7 +39,7 @@ module.exports.definition = { this._setProperty("flex", ""); this._setProperty("flex-grow", v); } else { - this._setProperty("flex-grow", module.exports.parse(v)); + this._setProperty("flex-grow", module.exports.parse(v, { globalObject: this._global })); } }, get() { diff --git a/lib/properties/flexShrink.js b/lib/properties/flexShrink.js index 4ae31ca3..cff60a04 100644 --- a/lib/properties/flexShrink.js +++ b/lib/properties/flexShrink.js @@ -2,14 +2,33 @@ const parsers = require("../parsers"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("flex-shrink", v)) { - return parsers.parseNumber(v, true); + const value = parsers.parsePropertyValue("flex-shrink", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, raw, type }] = value; + switch (type) { + case "Calc": { + if (isNumber) { + return raw; + } + break; + } + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseNumber(value); + } + } + } else if (typeof value === "string") { + return value; } }; @@ -20,7 +39,7 @@ module.exports.definition = { this._setProperty("flex", ""); this._setProperty("flex-shrink", v); } else { - this._setProperty("flex-shrink", module.exports.parse(v)); + this._setProperty("flex-shrink", module.exports.parse(v, { globalObject: this._global })); } }, get() { diff --git a/lib/properties/float.js b/lib/properties/float.js index e52b1350..cd4589ab 100644 --- a/lib/properties/float.js +++ b/lib/properties/float.js @@ -1,20 +1,39 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.isValidPropertyValue("float", v)) { - return strings.asciiLowercase(v); + } + const value = parsers.parsePropertyValue("float", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: + } + } else if (typeof value === "string") { + return value; } }; module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("float", module.exports.parse(v)); + this._setProperty( + "float", + module.exports.parse(v, { + globalObject: this._global + }) + ); }, get() { return this.getPropertyValue("float"); diff --git a/lib/properties/floodColor.js b/lib/properties/floodColor.js index 7a0cdf77..3cf1a3e2 100644 --- a/lib/properties/floodColor.js +++ b/lib/properties/floodColor.js @@ -2,18 +2,27 @@ const parsers = require("../parsers"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("flood-color", v)) { - const val = parsers.parseColor(v); - if (val) { - return val; + const value = parsers.parsePropertyValue("flood-color", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseColor(value); + } } - return parsers.parseKeyword(v); + } else if (typeof value === "string") { + return value; } }; @@ -23,7 +32,12 @@ module.exports.definition = { if (parsers.hasVarFunc(v)) { this._setProperty("flood-color", v); } else { - this._setProperty("flood-color", module.exports.parse(v)); + this._setProperty( + "flood-color", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/height.js b/lib/properties/height.js index 572cc468..13f2ed19 100644 --- a/lib/properties/height.js +++ b/lib/properties/height.js @@ -1,20 +1,35 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("height", v)) { - const val = parsers.parseMeasurement(v, true); - if (val) { - return val; + const value = parsers.parsePropertyValue("height", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, raw, type }] = value; + switch (type) { + case "Calc": { + if (!isNumber) { + return raw; + } + break; + } + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: { + return parsers.parseMeasurement(value); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; @@ -24,7 +39,12 @@ module.exports.definition = { if (parsers.hasVarFunc(v)) { this._setProperty("height", v); } else { - this._setProperty("height", module.exports.parse(v)); + this._setProperty( + "height", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/left.js b/lib/properties/left.js index 8605fe94..904f01f2 100644 --- a/lib/properties/left.js +++ b/lib/properties/left.js @@ -1,20 +1,35 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("left", v)) { - const val = parsers.parseMeasurement(v); - if (val) { - return val; + const value = parsers.parsePropertyValue("left", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, raw, type }] = value; + switch (type) { + case "Calc": { + if (!isNumber) { + return raw; + } + break; + } + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: { + return parsers.parseMeasurement(value); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; @@ -24,7 +39,12 @@ module.exports.definition = { if (parsers.hasVarFunc(v)) { this._setProperty("left", v); } else { - this._setProperty("left", module.exports.parse(v)); + this._setProperty( + "left", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/lightingColor.js b/lib/properties/lightingColor.js index 76978ed5..ea5bc839 100644 --- a/lib/properties/lightingColor.js +++ b/lib/properties/lightingColor.js @@ -2,18 +2,27 @@ const parsers = require("../parsers"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("lighting-color", v)) { - const val = parsers.parseColor(v); - if (val) { - return val; + const value = parsers.parsePropertyValue("lighting-color", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseColor(value); + } } - return parsers.parseKeyword(v); + } else if (typeof value === "string") { + return value; } }; @@ -23,7 +32,12 @@ module.exports.definition = { if (parsers.hasVarFunc(v)) { this._setProperty("lighting-color", v); } else { - this._setProperty("lighting-color", module.exports.parse(v)); + this._setProperty( + "lighting-color", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/margin.js b/lib/properties/margin.js index ce469c02..e3881cb4 100644 --- a/lib/properties/margin.js +++ b/lib/properties/margin.js @@ -1,22 +1,37 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); const positions = ["top", "right", "bottom", "left"]; -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("margin", v)) { - const val = parsers.parseMeasurement(v); - if (val) { - return val; + const value = parsers.parsePropertyValue("margin", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, raw, type }] = value; + switch (type) { + case "Calc": { + if (!isNumber) { + return raw; + } + break; + } + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: { + return parsers.parseMeasurement(value); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; diff --git a/lib/properties/marginBottom.js b/lib/properties/marginBottom.js index a0145a1c..316927ed 100644 --- a/lib/properties/marginBottom.js +++ b/lib/properties/marginBottom.js @@ -1,22 +1,37 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); const positions = ["top", "right", "bottom", "left"]; -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("margin-bottom", v)) { - const val = parsers.parseMeasurement(v); - if (val) { - return val; + const value = parsers.parsePropertyValue("margin-bottom", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, raw, type }] = value; + switch (type) { + case "Calc": { + if (!isNumber) { + return raw; + } + break; + } + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: { + return parsers.parseMeasurement(value); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; diff --git a/lib/properties/marginLeft.js b/lib/properties/marginLeft.js index c967b4e1..b7363cad 100644 --- a/lib/properties/marginLeft.js +++ b/lib/properties/marginLeft.js @@ -1,22 +1,37 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); const positions = ["top", "right", "bottom", "left"]; -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("margin-left", v)) { - const val = parsers.parseMeasurement(v); - if (val) { - return val; + const value = parsers.parsePropertyValue("margin-left", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, raw, type }] = value; + switch (type) { + case "Calc": { + if (!isNumber) { + return raw; + } + break; + } + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: { + return parsers.parseMeasurement(value); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; diff --git a/lib/properties/marginRight.js b/lib/properties/marginRight.js index dac2e3c8..571f6e2d 100644 --- a/lib/properties/marginRight.js +++ b/lib/properties/marginRight.js @@ -1,22 +1,37 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); const positions = ["top", "right", "bottom", "left"]; -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("margin-right", v)) { - const val = parsers.parseMeasurement(v); - if (val) { - return val; + const value = parsers.parsePropertyValue("margin-bottom", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, raw, type }] = value; + switch (type) { + case "Calc": { + if (!isNumber) { + return raw; + } + break; + } + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: { + return parsers.parseMeasurement(value); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; diff --git a/lib/properties/marginTop.js b/lib/properties/marginTop.js index eb473b00..1568e1b7 100644 --- a/lib/properties/marginTop.js +++ b/lib/properties/marginTop.js @@ -1,22 +1,37 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); const positions = ["top", "right", "bottom", "left"]; -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("margin-top", v)) { - const val = parsers.parseMeasurement(v); - if (val) { - return val; + const value = parsers.parsePropertyValue("margin-top", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, raw, type }] = value; + switch (type) { + case "Calc": { + if (!isNumber) { + return raw; + } + break; + } + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: { + return parsers.parseMeasurement(value); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; diff --git a/lib/properties/opacity.js b/lib/properties/opacity.js index 39c81121..aea7d5d9 100644 --- a/lib/properties/opacity.js +++ b/lib/properties/opacity.js @@ -1,17 +1,36 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.isValidPropertyValue("opacity", v)) { - const val = parsers.parseNumber(v) ?? parsers.parsePercent(v); - if (val) { - return val; + } + const value = parsers.parsePropertyValue("opacity", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, raw, type }] = value; + switch (type) { + case "Calc": { + return raw; + } + case "GlobalKeyword": + case "Identifier": { + return name; + } + case "Number": { + return parsers.parseNumber(value); + } + case "Percentage": { + return parsers.parsePercent(value); + } + default: } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; @@ -21,7 +40,12 @@ module.exports.definition = { if (parsers.hasVarFunc(v)) { this._setProperty("opacity", v); } else { - this._setProperty("opacity", module.exports.parse(v)); + this._setProperty( + "opacity", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/outlineColor.js b/lib/properties/outlineColor.js index c7166971..f5ff2998 100644 --- a/lib/properties/outlineColor.js +++ b/lib/properties/outlineColor.js @@ -2,18 +2,27 @@ const parsers = require("../parsers"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("outline-color", v)) { - const val = parsers.parseColor(v); - if (val) { - return val; + const value = parsers.parsePropertyValue("outline-color", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseColor(value); + } } - return parsers.parseKeyword(v); + } else if (typeof value === "string") { + return value; } }; @@ -23,7 +32,12 @@ module.exports.definition = { if (parsers.hasVarFunc(v)) { this._setProperty("outline-color", v); } else { - this._setProperty("outline-color", module.exports.parse(v)); + this._setProperty( + "outline-color", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/padding.js b/lib/properties/padding.js index f0112c27..9875a3a7 100644 --- a/lib/properties/padding.js +++ b/lib/properties/padding.js @@ -1,22 +1,38 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); const positions = ["top", "right", "bottom", "left"]; -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("padding", v)) { - const val = parsers.parseMeasurement(v, true); - if (val) { - return val; + const value = parsers.parsePropertyValue("padding", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, raw, type }] = value; + switch (type) { + case "Calc": { + if (!isNumber) { + return raw; + } + break; + } + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseMeasurement(value, { + min: 0 + }); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; diff --git a/lib/properties/paddingBottom.js b/lib/properties/paddingBottom.js index 8d7b9f66..e2d73aff 100644 --- a/lib/properties/paddingBottom.js +++ b/lib/properties/paddingBottom.js @@ -1,22 +1,38 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); const positions = ["top", "right", "bottom", "left"]; -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("padding-bottom", v)) { - const val = parsers.parseMeasurement(v, true); - if (val) { - return val; + const value = parsers.parsePropertyValue("padding-bottom", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, raw, type }] = value; + switch (type) { + case "Calc": { + if (!isNumber) { + return raw; + } + break; + } + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseMeasurement(value, { + min: 0 + }); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; diff --git a/lib/properties/paddingLeft.js b/lib/properties/paddingLeft.js index 9beaaefe..0f8874b9 100644 --- a/lib/properties/paddingLeft.js +++ b/lib/properties/paddingLeft.js @@ -1,22 +1,38 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); const positions = ["top", "right", "bottom", "left"]; -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("padding-left", v)) { - const val = parsers.parseMeasurement(v, true); - if (val) { - return val; + const value = parsers.parsePropertyValue("padding-left", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, raw, type }] = value; + switch (type) { + case "Calc": { + if (!isNumber) { + return raw; + } + break; + } + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseMeasurement(value, { + min: 0 + }); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; diff --git a/lib/properties/paddingRight.js b/lib/properties/paddingRight.js index a3d301a4..1521e21a 100644 --- a/lib/properties/paddingRight.js +++ b/lib/properties/paddingRight.js @@ -1,22 +1,38 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); const positions = ["top", "right", "bottom", "left"]; -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("padding-right", v)) { - const val = parsers.parseMeasurement(v, true); - if (val) { - return val; + const value = parsers.parsePropertyValue("padding-right", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, raw, type }] = value; + switch (type) { + case "Calc": { + if (!isNumber) { + return raw; + } + break; + } + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseMeasurement(value, { + min: 0 + }); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; diff --git a/lib/properties/paddingTop.js b/lib/properties/paddingTop.js index 2e23b8eb..641d1843 100644 --- a/lib/properties/paddingTop.js +++ b/lib/properties/paddingTop.js @@ -1,22 +1,38 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); const positions = ["top", "right", "bottom", "left"]; -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("padding-top", v)) { - const val = parsers.parseMeasurement(v, true); - if (val) { - return val; + const value = parsers.parsePropertyValue("padding-top", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, raw, type }] = value; + switch (type) { + case "Calc": { + if (!isNumber) { + return raw; + } + break; + } + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseMeasurement(value, { + min: 0 + }); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; diff --git a/lib/properties/right.js b/lib/properties/right.js index 8b700d0a..d0e823c6 100644 --- a/lib/properties/right.js +++ b/lib/properties/right.js @@ -1,20 +1,35 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("right", v)) { - const val = parsers.parseMeasurement(v); - if (val) { - return val; + const value = parsers.parsePropertyValue("right", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, raw, type }] = value; + switch (type) { + case "Calc": { + if (!isNumber) { + return raw; + } + break; + } + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: { + return parsers.parseMeasurement(value); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; @@ -24,7 +39,12 @@ module.exports.definition = { if (parsers.hasVarFunc(v)) { this._setProperty("right", v); } else { - this._setProperty("right", module.exports.parse(v)); + this._setProperty( + "right", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/stopColor.js b/lib/properties/stopColor.js index 66a50bf6..a6afe2d6 100644 --- a/lib/properties/stopColor.js +++ b/lib/properties/stopColor.js @@ -1,20 +1,28 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("stop-color", v)) { - const val = parsers.parseColor(v); - if (val) { - return val; + const value = parsers.parsePropertyValue("stop-color", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseColor(value); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; @@ -24,7 +32,12 @@ module.exports.definition = { if (parsers.hasVarFunc(v)) { this._setProperty("stop-color", v); } else { - this._setProperty("stop-color", module.exports.parse(v)); + this._setProperty( + "stop-color", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/top.js b/lib/properties/top.js index 86b71f5a..1c709a9f 100644 --- a/lib/properties/top.js +++ b/lib/properties/top.js @@ -1,20 +1,35 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("top", v)) { - const val = parsers.parseMeasurement(v); - if (val) { - return val; + const value = parsers.parsePropertyValue("top", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, raw, type }] = value; + switch (type) { + case "Calc": { + if (!isNumber) { + return raw; + } + break; + } + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: { + return parsers.parseMeasurement(value); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; @@ -24,7 +39,12 @@ module.exports.definition = { if (parsers.hasVarFunc(v)) { this._setProperty("top", v); } else { - this._setProperty("top", module.exports.parse(v)); + this._setProperty( + "top", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/webkitBorderAfterColor.js b/lib/properties/webkitBorderAfterColor.js index 47d63b18..ed86b422 100644 --- a/lib/properties/webkitBorderAfterColor.js +++ b/lib/properties/webkitBorderAfterColor.js @@ -2,18 +2,39 @@ const parsers = require("../parsers"); -module.exports.parse = function parse(v) { - const val = parsers.parseColor(v); - if (val) { - return val; +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; + if (v === "") { + return v; + } + const value = parsers.parsePropertyValue("-webkit-border-after-color", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseColor(value); + } + } + } else if (typeof value === "string") { + return value; } - return parsers.parseKeyword(v); }; module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("-webkit-border-after-color", module.exports.parse(v)); + this._setProperty( + "-webkit-border-after-color", + module.exports.parse(v, { + globalObject: this._global + }) + ); }, get() { return this.getPropertyValue("-webkit-border-after-color"); diff --git a/lib/properties/webkitBorderBeforeColor.js b/lib/properties/webkitBorderBeforeColor.js index 204e04c9..4dba364e 100644 --- a/lib/properties/webkitBorderBeforeColor.js +++ b/lib/properties/webkitBorderBeforeColor.js @@ -2,18 +2,39 @@ const parsers = require("../parsers"); -module.exports.parse = function parse(v) { - const val = parsers.parseColor(v); - if (val) { - return val; +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; + if (v === "") { + return v; + } + const value = parsers.parsePropertyValue("-webkit-border-before-color", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseColor(value); + } + } + } else if (typeof value === "string") { + return value; } - return parsers.parseKeyword(v); }; module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("-webkit-border-before-color", module.exports.parse(v)); + this._setProperty( + "-webkit-border-before-color", + module.exports.parse(v, { + globalObject: this._global + }) + ); }, get() { return this.getPropertyValue("-webkit-border-before-color"); diff --git a/lib/properties/webkitBorderEndColor.js b/lib/properties/webkitBorderEndColor.js index 033ece75..fbaafef1 100644 --- a/lib/properties/webkitBorderEndColor.js +++ b/lib/properties/webkitBorderEndColor.js @@ -2,18 +2,39 @@ const parsers = require("../parsers"); -module.exports.parse = function parse(v) { - const val = parsers.parseColor(v); - if (val) { - return val; +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; + if (v === "") { + return v; + } + const value = parsers.parsePropertyValue("-webkit-border-end-color", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseColor(value); + } + } + } else if (typeof value === "string") { + return value; } - return parsers.parseKeyword(v); }; module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("-webkit-border-end-color", module.exports.parse(v)); + this._setProperty( + "-webkit-border-end-color", + module.exports.parse(v, { + globalObject: this._global + }) + ); }, get() { return this.getPropertyValue("-webkit-border-end-color"); diff --git a/lib/properties/webkitBorderStartColor.js b/lib/properties/webkitBorderStartColor.js index 01e3c81f..e5c080bf 100644 --- a/lib/properties/webkitBorderStartColor.js +++ b/lib/properties/webkitBorderStartColor.js @@ -2,18 +2,39 @@ const parsers = require("../parsers"); -module.exports.parse = function parse(v) { - const val = parsers.parseColor(v); - if (val) { - return val; +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; + if (v === "") { + return v; + } + const value = parsers.parsePropertyValue("-webkit-border-start-color", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseColor(value); + } + } + } else if (typeof value === "string") { + return value; } - return parsers.parseKeyword(v); }; module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("-webkit-border-start-color", module.exports.parse(v)); + this._setProperty( + "-webkit-border-start-color", + module.exports.parse(v, { + globalObject: this._global + }) + ); }, get() { return this.getPropertyValue("-webkit-border-start-color"); diff --git a/lib/properties/webkitColumnRuleColor.js b/lib/properties/webkitColumnRuleColor.js index df16e52f..7fd53f41 100644 --- a/lib/properties/webkitColumnRuleColor.js +++ b/lib/properties/webkitColumnRuleColor.js @@ -2,18 +2,39 @@ const parsers = require("../parsers"); -module.exports.parse = function parse(v) { - const val = parsers.parseColor(v); - if (val) { - return val; +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; + if (v === "") { + return v; + } + const value = parsers.parsePropertyValue("-webkit-column-rule-color", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseColor(value); + } + } + } else if (typeof value === "string") { + return value; } - return parsers.parseKeyword(v); }; module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("-webkit-column-rule-color", module.exports.parse(v)); + this._setProperty( + "-webkit-column-rule-color", + module.exports.parse(v, { + globalObject: this._global + }) + ); }, get() { return this.getPropertyValue("-webkit-column-rule-color"); diff --git a/lib/properties/webkitTapHighlightColor.js b/lib/properties/webkitTapHighlightColor.js index d219ed96..d4c3a648 100644 --- a/lib/properties/webkitTapHighlightColor.js +++ b/lib/properties/webkitTapHighlightColor.js @@ -2,18 +2,39 @@ const parsers = require("../parsers"); -module.exports.parse = function parse(v) { - const val = parsers.parseColor(v); - if (val) { - return val; +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; + if (v === "") { + return v; + } + const value = parsers.parsePropertyValue("-webkit-tap-highlight-color", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseColor(value); + } + } + } else if (typeof value === "string") { + return value; } - return parsers.parseKeyword(v); }; module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("-webkit-tap-highlight-color", module.exports.parse(v)); + this._setProperty( + "-webkit-tap-highlight-color", + module.exports.parse(v, { + globalObject: this._global + }) + ); }, get() { return this.getPropertyValue("-webkit-tap-highlight-color"); diff --git a/lib/properties/webkitTextEmphasisColor.js b/lib/properties/webkitTextEmphasisColor.js index a0adf738..2c3af93d 100644 --- a/lib/properties/webkitTextEmphasisColor.js +++ b/lib/properties/webkitTextEmphasisColor.js @@ -2,18 +2,39 @@ const parsers = require("../parsers"); -module.exports.parse = function parse(v) { - const val = parsers.parseColor(v); - if (val) { - return val; +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; + if (v === "") { + return v; + } + const value = parsers.parsePropertyValue("-webkit-text-emphasis-color", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseColor(value); + } + } + } else if (typeof value === "string") { + return value; } - return parsers.parseKeyword(v); }; module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("-webkit-text-emphasis-color", module.exports.parse(v)); + this._setProperty( + "-webkit-text-emphasis-color", + module.exports.parse(v, { + globalObject: this._global + }) + ); }, get() { return this.getPropertyValue("-webkit-text-emphasis-color"); diff --git a/lib/properties/webkitTextFillColor.js b/lib/properties/webkitTextFillColor.js index 9667dba1..eaa3404c 100644 --- a/lib/properties/webkitTextFillColor.js +++ b/lib/properties/webkitTextFillColor.js @@ -2,18 +2,39 @@ const parsers = require("../parsers"); -module.exports.parse = function parse(v) { - const val = parsers.parseColor(v); - if (val) { - return val; +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; + if (v === "") { + return v; + } + const value = parsers.parsePropertyValue("-webkit-text-fill-color", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseColor(value); + } + } + } else if (typeof value === "string") { + return value; } - return parsers.parseKeyword(v); }; module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("-webkit-text-fill-color", module.exports.parse(v)); + this._setProperty( + "-webkit-text-fill-color", + module.exports.parse(v, { + globalObject: this._global + }) + ); }, get() { return this.getPropertyValue("-webkit-text-fill-color"); diff --git a/lib/properties/webkitTextStrokeColor.js b/lib/properties/webkitTextStrokeColor.js index e3486e33..fc57e713 100644 --- a/lib/properties/webkitTextStrokeColor.js +++ b/lib/properties/webkitTextStrokeColor.js @@ -2,18 +2,39 @@ const parsers = require("../parsers"); -module.exports.parse = function parse(v) { - const val = parsers.parseColor(v); - if (val) { - return val; +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; + if (v === "") { + return v; + } + const value = parsers.parsePropertyValue("-webkit-text-stroke-color", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": { + return name; + } + default: { + return parsers.parseColor(value); + } + } + } else if (typeof value === "string") { + return value; } - return parsers.parseKeyword(v); }; module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - this._setProperty("-webkit-text-stroke-color", module.exports.parse(v)); + this._setProperty( + "-webkit-text-stroke-color", + module.exports.parse(v, { + globalObject: this._global + }) + ); }, get() { return this.getPropertyValue("-webkit-text-stroke-color"); diff --git a/lib/properties/width.js b/lib/properties/width.js index 2eeb573b..d2389c61 100644 --- a/lib/properties/width.js +++ b/lib/properties/width.js @@ -1,20 +1,35 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("width", v)) { - const val = parsers.parseMeasurement(v, true); - if (val) { - return val; + const value = parsers.parsePropertyValue("width", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, raw, type }] = value; + switch (type) { + case "Calc": { + if (!isNumber) { + return raw; + } + break; + } + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: { + return parsers.parseMeasurement(value); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; @@ -24,7 +39,12 @@ module.exports.definition = { if (parsers.hasVarFunc(v)) { this._setProperty("width", v); } else { - this._setProperty("width", module.exports.parse(v)); + this._setProperty( + "width", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/test/parsers.test.js b/test/parsers.test.js index 628d34b7..8077ab02 100644 --- a/test/parsers.test.js +++ b/test/parsers.test.js @@ -118,6 +118,36 @@ describe("prepareValue", () => { }); }); +describe("isGlobalKeyword", () => { + it("should return false", () => { + const input = ""; + const output = parsers.isGlobalKeyword(input); + + assert.strictEqual(output, false); + }); + + it("should return false", () => { + const input = "foo"; + const output = parsers.isGlobalKeyword(input); + + assert.strictEqual(output, false); + }); + + it("should return true", () => { + const input = "initial"; + const output = parsers.isGlobalKeyword(input); + + assert.strictEqual(output, true); + }); + + it("should return true", () => { + const input = "INITIAL"; + const output = parsers.isGlobalKeyword(input); + + assert.strictEqual(output, true); + }); +}); + describe("hasVarFunc", () => { it("should return false", () => { const input = ""; @@ -186,6 +216,15 @@ describe("parseCalc", () => { assert.strictEqual(output, ""); }); + it('should return "calc(6)"', () => { + const input = "calc(2 * 3)"; + const output = parsers.parseCalc(input, { + format: "specifiedValue" + }); + + assert.strictEqual(output, "calc(6)"); + }); + it('should return "calc(6px)"', () => { const input = "calc(2px * 3)"; const output = parsers.parseCalc(input, { @@ -201,6 +240,13 @@ describe("parseCalc", () => { assert.strictEqual(output, "rgb(calc(255/3) 0 0)"); }); + + it('should return "calc(100% - 2em)"', () => { + const input = "calc(100% - 2em)"; + const output = parsers.parseCalc(input); + + assert.strictEqual(output, "calc(100% - 2em)"); + }); }); describe("parseNumber", () => { @@ -218,13 +264,6 @@ describe("parseNumber", () => { assert.strictEqual(output, undefined); }); - it("should return undefined", () => { - const input = "-1"; - const output = parsers.parseNumber(input, true); - - assert.strictEqual(output, undefined); - }); - it('should return "1"', () => { const input = "1"; const output = parsers.parseNumber(input); @@ -252,6 +291,48 @@ describe("parseNumber", () => { assert.strictEqual(output, "calc(0.666667)"); }); + + it("should return undefined", () => { + const input = "-50"; + const output = parsers.parseNumber(input, { + min: 0, + max: 100 + }); + + assert.strictEqual(output, undefined); + }); + + it("should return undefined", () => { + const input = "150"; + const output = parsers.parseNumber(input, { + min: 0, + max: 100 + }); + + assert.strictEqual(output, undefined); + }); + + it("should return clamped value", () => { + const input = "-50"; + const output = parsers.parseNumber(input, { + min: 0, + max: 100, + clamp: true + }); + + assert.strictEqual(output, "0"); + }); + + it("should return clamped value", () => { + const input = "150"; + const output = parsers.parseNumber(input, { + min: 0, + max: 100, + clamp: true + }); + + assert.strictEqual(output, "100"); + }); }); describe("parseLength", () => { @@ -262,13 +343,20 @@ describe("parseLength", () => { assert.strictEqual(output, ""); }); - it("should return undefined for negative length", () => { - const input = "-1em"; - const output = parsers.parseLength(input, true); + it("should return empty string", () => { + const input = "100"; + const output = parsers.parseLength(input); assert.strictEqual(output, undefined); }); + it("should return value", () => { + const input = "10px"; + const output = parsers.parseLength(input); + + assert.strictEqual(output, "10px"); + }); + it("should return value as is", () => { const input = "var(/* comment */ --foo)"; const output = parsers.parseLength(input); @@ -296,6 +384,48 @@ describe("parseLength", () => { assert.strictEqual(output, "calc(10px + 100vh)"); }); + + it("should return undefined", () => { + const input = "-50px"; + const output = parsers.parseLength(input, { + min: 0, + max: 100 + }); + + assert.strictEqual(output, undefined); + }); + + it("should return undefined", () => { + const input = "150px"; + const output = parsers.parseLength(input, { + min: 0, + max: 100 + }); + + assert.strictEqual(output, undefined); + }); + + it("should return clamped value", () => { + const input = "-50px"; + const output = parsers.parseLength(input, { + min: 0, + max: 100, + clamp: true + }); + + assert.strictEqual(output, "0px"); + }); + + it("should return clamped value", () => { + const input = "150px"; + const output = parsers.parseLength(input, { + min: 0, + max: 100, + clamp: true + }); + + assert.strictEqual(output, "100px"); + }); }); describe("parsePercent", () => { @@ -306,18 +436,18 @@ describe("parsePercent", () => { assert.strictEqual(output, ""); }); - it("should return value", () => { - const input = "10%"; + it("should return empty string", () => { + const input = "100"; const output = parsers.parsePercent(input); - assert.strictEqual(output, "10%"); + assert.strictEqual(output, undefined); }); - it("should return undefined for negative percent", () => { - const input = "-10%"; - const output = parsers.parsePercent(input, true); + it("should return value", () => { + const input = "10%"; + const output = parsers.parsePercent(input); - assert.strictEqual(output, undefined); + assert.strictEqual(output, "10%"); }); it("should return value as is", () => { @@ -340,6 +470,48 @@ describe("parsePercent", () => { assert.strictEqual(output, "calc(20% + 10px)"); }); + + it("should return undefined", () => { + const input = "-50%"; + const output = parsers.parsePercent(input, { + min: 0, + max: 100 + }); + + assert.strictEqual(output, undefined); + }); + + it("should return undefined", () => { + const input = "150%"; + const output = parsers.parsePercent(input, { + min: 0, + max: 100 + }); + + assert.strictEqual(output, undefined); + }); + + it("should return clamped value", () => { + const input = "-50%"; + const output = parsers.parsePercent(input, { + min: 0, + max: 100, + clamp: true + }); + + assert.strictEqual(output, "0%"); + }); + + it("should return clamped value", () => { + const input = "150%"; + const output = parsers.parsePercent(input, { + min: 0, + max: 100, + clamp: true + }); + + assert.strictEqual(output, "100%"); + }); }); describe("parseMeasurement", () => { @@ -350,9 +522,9 @@ describe("parseMeasurement", () => { assert.strictEqual(output, ""); }); - it("should return undefined", () => { - const input = "-1em"; - const output = parsers.parseMeasurement(input, true); + it("should return empty string", () => { + const input = "100"; + const output = parsers.parseMeasurement(input); assert.strictEqual(output, undefined); }); @@ -419,6 +591,48 @@ describe("parseMeasurement", () => { assert.strictEqual(output, "0px"); }); + + it("should return undefined", () => { + const input = "-1em"; + const output = parsers.parseMeasurement(input, { + min: 0, + max: 1 + }); + + assert.strictEqual(output, undefined); + }); + + it("should return undefined", () => { + const input = "1.1em"; + const output = parsers.parseMeasurement(input, { + min: 0, + max: 1 + }); + + assert.strictEqual(output, undefined); + }); + + it("should return clamped value", () => { + const input = "-1em"; + const output = parsers.parseMeasurement(input, { + min: 0, + max: 1, + clamp: true + }); + + assert.strictEqual(output, "0em"); + }); + + it("should return clamped value", () => { + const input = "1.1em"; + const output = parsers.parseMeasurement(input, { + min: 0, + max: 1, + clamp: true + }); + + assert.strictEqual(output, "1em"); + }); }); describe("parseAngle", () => { @@ -429,6 +643,13 @@ describe("parseAngle", () => { assert.strictEqual(output, ""); }); + it("should return undefined", () => { + const input = "90"; + const output = parsers.parseAngle(input); + + assert.strictEqual(output, undefined); + }); + it("should return value with deg unit", () => { const input = "90deg"; const output = parsers.parseAngle(input); @@ -437,10 +658,10 @@ describe("parseAngle", () => { }); it("should return value with deg unit", () => { - const input = "480deg"; + const input = "450deg"; const output = parsers.parseAngle(input); - assert.strictEqual(output, "120deg"); + assert.strictEqual(output, "450deg"); }); it("should return value with deg unit", () => { @@ -451,17 +672,54 @@ describe("parseAngle", () => { }); it("should return value with deg unit", () => { - const input = "270deg"; - const output = parsers.parseAngle(input, true); + const input = "100grad"; + const output = parsers.parseAngle(input); - assert.strictEqual(output, "270deg"); + assert.strictEqual(output, "100grad"); }); - it("should return value with grad unit", () => { - const input = "100grad"; - const output = parsers.parseAngle(input, true); + it("should return value with deg unit", () => { + const input = "500grad"; + const output = parsers.parseAngle(input); - assert.strictEqual(output, "100grad"); + assert.strictEqual(output, "500grad"); + }); + + it("should return value with deg unit", () => { + const input = "-100grad"; + const output = parsers.parseAngle(input); + + assert.strictEqual(output, "-100grad"); + }); + + it("should return value with rad unit", () => { + const input = "1.57rad"; + const output = parsers.parseAngle(input); + + assert.strictEqual(output, "1.57rad"); + }); + + it("should return value with rad unit", () => { + const input = "-1.57rad"; + const output = parsers.parseAngle(input); + + assert.strictEqual(output, "-1.57rad"); + }); + + it("should return value with turn unit", () => { + const input = "0.25turn"; + const output = parsers.parseAngle(input, { + min: 0 + }); + + assert.strictEqual(output, "0.25turn"); + }); + + it("should return value with turn unit", () => { + const input = "-0.25turn"; + const output = parsers.parseAngle(input); + + assert.strictEqual(output, "-0.25turn"); }); it("should return value as is", () => { diff --git a/test/properties.test.js b/test/properties.test.js index 537b2288..9cfe98a6 100644 --- a/test/properties.test.js +++ b/test/properties.test.js @@ -717,6 +717,21 @@ describe("border", () => { ); }); + // FIXME: + it.skip("border-style should set / get keyword", () => { + testImplicitPropertyValue( + "border-style", + "none", + "none", + new Map([ + ["border-top-style", "none"], + ["border-right-style", "none"], + ["border-bottom-style", "none"], + ["border-left-style", "none"] + ]) + ); + }); + it("border-style should set / get keyword", () => { testImplicitPropertyValue( "border-style", @@ -1101,6 +1116,20 @@ describe("box model", () => { ); }); + it("margin shorthand should set / get value", () => { + testImplicitPropertyValue( + "margin", + "initial", + "initial", + new Map([ + ["margin-top", "initial"], + ["margin-right", "initial"], + ["margin-bottom", "initial"], + ["margin-left", "initial"] + ]) + ); + }); + it("padding-top should set / get length", () => { testPropertyValue("padding-top", "0", "0px"); }); @@ -1250,6 +1279,20 @@ describe("box model", () => { ]) ); }); + + it("padding shorthand should set / get value", () => { + testImplicitPropertyValue( + "padding", + "initial", + "initial", + new Map([ + ["padding-top", "initial"], + ["padding-right", "initial"], + ["padding-bottom", "initial"], + ["padding-left", "initial"] + ]) + ); + }); }); describe("box sizing", () => { @@ -1615,6 +1658,18 @@ describe("flex box", () => { testPropertyValue("flex", "initial", "initial"); }); + it("flex should set / get keyword", () => { + testPropertyValue("flex", "unset", "unset"); + }); + + it("flex shorthand should not set / get longhand value", () => { + testPropertyValue("flex", "2 1 3", ""); + }); + + it("flex shorthand should not set / get longhand value", () => { + testPropertyValue("flex", "2 1 calc(3)", ""); + }); + it("flex shorthand should set / get longhand value", () => { testPropertyValue("flex", "2", "2 1 0%"); }); @@ -1647,6 +1702,45 @@ describe("flex box", () => { ]) ); }); + + it("flex shorthand should set / get longhand value", () => { + testImplicitPropertyValue( + "flex", + "calc(2px * 3)", + "1 1 calc(6px)", + new Map([ + ["flex-grow", "1"], + ["flex-shrink", "1"], + ["flex-basis", "calc(6px)"] + ]) + ); + }); + + it("flex shorthand should set / get longhand value", () => { + testImplicitPropertyValue( + "flex", + "calc(2 * 3)", + "calc(6) 1 0%", + new Map([ + ["flex-grow", "calc(6)"], + ["flex-shrink", "1"], + ["flex-basis", "0%"] + ]) + ); + }); + + it("flex shorthand should set / get longhand value", () => { + testImplicitPropertyValue( + "flex", + "initial", + "initial", + new Map([ + ["flex-grow", "initial"], + ["flex-shrink", "initial"], + ["flex-basis", "initial"] + ]) + ); + }); }); describe("font", () => { From cd86606f031fdd5092e8717fbf563964d1653b1a Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Sun, 10 Aug 2025 12:57:47 +0900 Subject: [PATCH 34/41] Update fonts --- lib/parsers.js | 7 +- lib/properties/font.js | 166 +++++++++++++++++++++++++--------- lib/properties/fontFamily.js | 129 ++++++++++++-------------- lib/properties/fontSize.js | 42 +++++++-- lib/properties/fontStyle.js | 38 +++++++- lib/properties/fontVariant.js | 48 ++++++++-- lib/properties/fontWeight.js | 47 +++++++--- lib/properties/lineHeight.js | 44 +++++++-- test/properties.test.js | 93 ++++++++++++++++++- 9 files changed, 455 insertions(+), 159 deletions(-) diff --git a/lib/parsers.js b/lib/parsers.js index 0b64d7e4..9b25f9be 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -714,7 +714,7 @@ exports.parseShorthand = function parseShorthand(val, shorthandFor, opt = {}) { // Parse property value. Returns string or array of parsed object. exports.parsePropertyValue = function parsePropertyValue(prop, val, opt = {}) { - const { globalObject, inArray } = opt; + const { caseSensitive, globalObject, inArray } = opt; val = exports.prepareValue(val, globalObject); if (val === "" || exports.hasVarFunc(val)) { return val; @@ -727,7 +727,10 @@ exports.parsePropertyValue = function parsePropertyValue(prop, val, opt = {}) { } val = parsedValue; } - const value = asciiLowercase(val); + let value = val; + if (!caseSensitive) { + value = asciiLowercase(val); + } if (GLOBAL_KEY.includes(value)) { if (inArray) { return [ diff --git a/lib/properties/font.js b/lib/properties/font.js index ef6986f5..2a5116bf 100644 --- a/lib/properties/font.js +++ b/lib/properties/font.js @@ -8,8 +8,6 @@ const fontSize = require("./fontSize"); const lineHeight = require("./lineHeight"); const fontFamily = require("./fontFamily"); -const keywords = ["caption", "icon", "menu", "message-box", "small-caption", "status-bar"]; - module.exports.shorthandFor = new Map([ ["font-style", fontStyle], ["font-variant", fontVariant], @@ -19,17 +17,15 @@ module.exports.shorthandFor = new Map([ ["font-family", fontFamily] ]); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; } else if (parsers.hasCalcFunc(v)) { v = parsers.parseCalc(v); - } else if (!parsers.isValidPropertyValue("font", v)) { - return; } - const key = parsers.parseKeyword(v, keywords); - if (key) { - return key; + if (!parsers.isValidPropertyValue("font", v)) { + return; } const [fontBlock, ...families] = parsers.splitValue(v, { delimiter: "," @@ -45,13 +41,21 @@ module.exports.parse = function parse(v) { const fontFamilies = new Set(); if (fontBlockB) { const [lineB, ...familiesB] = fontBlockB.trim().split(" "); - if (!lineB || !parsers.isValidPropertyValue("line-height", lineB) || !familiesB.length) { + if (!lineB || !familiesB.length) { return; } - const lineHeightB = lineHeight.parse(lineB); - const familyB = familiesB.join(" "); - if (parsers.isValidPropertyValue("font-family", familyB)) { - fontFamilies.add(fontFamily.parse(familyB)); + const lineHeightB = lineHeight.parse(lineB, { + global + }); + if (typeof lineHeightB !== "string") { + return; + } + const familyB = fontFamily.parse(familiesB.join(" "), { + globalObject, + caseSensitive: true + }); + if (typeof familyB === "string") { + fontFamilies.add(familyB); } else { return; } @@ -63,19 +67,40 @@ module.exports.parse = function parse(v) { } else { for (const property of properties) { switch (property) { + case "font-size": { + const parsedValue = fontSize.parse(part, { + globalObject + }); + if (typeof parsedValue === "string") { + font[property] = parsedValue; + } + break; + } case "font-style": - case "font-variant": case "font-weight": { - if (font[property] === "normal" && parsers.isValidPropertyValue(property, part)) { - const value = module.exports.shorthandFor.get(property); - font[property] = value.parse(part); + if (font[property] === "normal") { + const longhand = module.exports.shorthandFor.get(property); + const parsedValue = longhand.parse(part, { + globalObject + }); + if (typeof parsedValue === "string") { + font[property] = parsedValue; + } } break; } - case "font-size": { - if (parsers.isValidPropertyValue(property, part)) { - const value = module.exports.shorthandFor.get(property); - font[property] = value.parse(part); + case "font-variant": { + if (font[property] === "normal") { + const parsedValue = fontVariant.parse(part, { + globalObject + }); + if (typeof parsedValue === "string") { + if (parsedValue === "small-cap") { + font[property] = parsedValue; + } else if (parsedValue !== "normal") { + return; + } + } } break; } @@ -91,27 +116,66 @@ module.exports.parse = function parse(v) { } } else { const revParts = parsers.splitValue(fontBlockA.trim()).toReversed(); - const revFontFamily = []; + if (revParts.length === 1) { + const [part] = revParts; + const value = parsers.parsePropertyValue("font", part, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + if (type === "GlobalKeyword") { + return { + "font-style": name, + "font-variant": name, + "font-weight": name, + "font-size": name, + "line-height": name, + "font-family": name + }; + } + } + return; + } const properties = ["font-style", "font-variant", "font-weight", "line-height"]; - font["font-style"] = "normal"; - font["font-variant"] = "normal"; - font["font-weight"] = "normal"; - font["line-height"] = "normal"; + for (const property of properties) { + font[property] = "normal"; + } + const revFontFamily = []; let fontSizeA; for (const part of revParts) { if (fontSizeA) { - if (part === "normal") { + if (/^normal$/i.test(part)) { continue; } else { for (const property of properties) { switch (property) { case "font-style": - case "font-variant": case "font-weight": case "line-height": { - if (parsers.isValidPropertyValue(property, part)) { - const value = module.exports.shorthandFor.get(property); - font[property] = value.parse(part); + if (font[property] === "normal") { + const longhand = module.exports.shorthandFor.get(property); + const parsedValue = longhand.parse(part, { + globalObject + }); + if (typeof parsedValue === "string") { + font[property] = parsedValue; + } + } + break; + } + case "font-variant": { + if (font[property] === "normal") { + const parsedValue = fontVariant.parse(part, { + globalObject + }); + if (typeof parsedValue === "string") { + if (parsedValue === "small-cap") { + font[property] = parsedValue; + } else if (parsedValue !== "normal") { + return; + } + } } break; } @@ -119,16 +183,30 @@ module.exports.parse = function parse(v) { } } } - } else if (parsers.isValidPropertyValue("font-size", part)) { - fontSizeA = fontSize.parse(part); - } else if (parsers.isValidPropertyValue("font-family", part)) { - revFontFamily.push(part); } else { - return; + const parsedFontSize = fontSize.parse(part, { + globalObject + }); + if (typeof parsedFontSize === "string") { + fontSizeA = parsedFontSize; + } else { + const parsedFontFamily = fontFamily.parse(part, { + globalObject, + caseSensitive: true + }); + if (typeof parsedFontFamily === "string") { + revFontFamily.push(parsedFontFamily); + } else { + return; + } + } } } - const family = revFontFamily.reverse().join(" "); - if (fontSizeA && parsers.isValidPropertyValue("font-family", family)) { + const family = fontFamily.parse(revFontFamily.toReversed().join(" "), { + globalObject, + caseSensitive: true + }); + if (fontSizeA && family) { font["font-size"] = fontSizeA; fontFamilies.add(fontFamily.parse(family)); } else { @@ -136,8 +214,12 @@ module.exports.parse = function parse(v) { } } for (const family of families) { - if (parsers.isValidPropertyValue("font-family", family)) { - fontFamilies.add(fontFamily.parse(family)); + const parsedFontFamily = fontFamily.parse(family, { + globalObject, + caseSensitive: true + }); + if (parsedFontFamily) { + fontFamilies.add(parsedFontFamily); } else { return; } @@ -155,7 +237,9 @@ module.exports.definition = { } this._setProperty("font", v); } else { - const obj = module.exports.parse(v); + const obj = module.exports.parse(v, { + globalObject: this._global + }); if (!obj) { return; } diff --git a/lib/properties/fontFamily.js b/lib/properties/fontFamily.js index b61333e7..b0286581 100644 --- a/lib/properties/fontFamily.js +++ b/lib/properties/fontFamily.js @@ -2,85 +2,67 @@ const parsers = require("../parsers"); -const keywords = [ - "serif", - "sans-serif", - "cursive", - "fantasy", - "monospace", - "system-ui", - "math", - "ui-serif", - "ui-sans-serif", - "ui-monospace", - "ui-rounded" -]; -const genericValues = ["fangsong", "kai", "khmer-mul", "nastaliq"]; - -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; } - if (!parsers.isValidPropertyValue("font-family", v)) { - return; - } - const val = parsers.splitValue(v, { + const values = parsers.splitValue(v, { delimiter: "," }); - const font = []; - let valid = false; - for (const i of val) { - const str = parsers.parseString(i); - if (str) { - font.push(str); - valid = true; - continue; - } - const key = parsers.parseKeyword(i, keywords); - if (key) { - font.push(key); - valid = true; - continue; - } - const obj = parsers.parseFunction(i); - if (obj) { - const { name, value } = obj; - if (name === "generic" && genericValues.includes(value)) { - font.push(`${name}(${value})`); - valid = true; - continue; + const parsedValues = []; + for (const val of values) { + const value = parsers.parsePropertyValue("font-family", val, { + globalObject, + caseSensitive: true, + inArray: true + }); + if (Array.isArray(value) && value.length) { + if (value.length === 1) { + const [{ name, type, value: itemValue }] = value; + switch (type) { + case "Function": { + parsedValues.push(`${name}(${itemValue})`); + break; + } + case "GlobalKeyword": + case "Identifier": { + if (name !== "undefined") { + parsedValues.push(name); + } + break; + } + case "String": { + const parsedValue = itemValue.replaceAll("\\", "").replaceAll('"', '\\"'); + parsedValues.push(`"${parsedValue}"`); + break; + } + default: { + return; + } + } + } else { + const parts = []; + for (const item of value) { + const { name, type } = item; + if (type === "Identifier") { + parts.push(name); + } else { + return; + } + } + const parsedValue = parts.join(" ").replaceAll("\\", "").replaceAll('"', '\\"'); + parsedValues.push(`"${parsedValue}"`); } - } - // This implementation does not strictly follow the specification. - // The spec does not require the first letter of the font-family to be - // capitalized, and unquoted font-family names are not restricted to ASCII. - // However, in the real world, the first letter of the ASCII font-family - // names are capitalized, and unquoted font-family names do not contain - // spaces, e.g. `Times`. And non-ASCII font-family names are quoted even - // without spaces, e.g. `"メイリオ"`. - // @see https://drafts.csswg.org/css-fonts/#font-family-prop - if ( - i !== "undefined" && - /^(?:[A-Z][A-Za-z\d-]+(?:\s+[A-Z][A-Za-z\d-]+)*|-?[a-z][a-z-]+)$/.test(i) - ) { - font.push(i.trim()); - valid = true; - continue; - } - if ( - i !== "undefined" && - /^(?:[A-Z][A-Za-z\d\\-]+(?:\s+[A-Z][A-Za-z\d\\-]+)*|-?[a-z][a-z-]+)$/.test(i) - ) { - const j = i.replaceAll("\\", "").replaceAll('"', '\\"').trim(); - font.push(`"${j}"`); - valid = true; - continue; - } - if (!valid) { + } else if (typeof value === "string") { + parsedValues.push(value); + } else { return; } } - return font.join(", "); + if (parsedValues.length) { + return parsedValues.join(", "); + } }; module.exports.definition = { @@ -90,7 +72,12 @@ module.exports.definition = { this._setProperty("font", ""); this._setProperty("font-family", v); } else { - this._setProperty("font-family", module.exports.parse(v)); + this._setProperty( + "font-family", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/fontSize.js b/lib/properties/fontSize.js index acbcb021..c3df05de 100644 --- a/lib/properties/fontSize.js +++ b/lib/properties/fontSize.js @@ -1,20 +1,37 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("font-size", v)) { - const val = parsers.parseMeasurement(v, true); - if (val) { - return val; + const value = parsers.parsePropertyValue("font-size", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, raw, type }] = value; + switch (type) { + case "Calc": { + if (!isNumber) { + return raw; + } + break; + } + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: { + return parsers.parseMeasurement(value, { + min: 0 + }); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; @@ -25,7 +42,12 @@ module.exports.definition = { this._setProperty("font", ""); this._setProperty("font-size", v); } else { - this._setProperty("font-size", module.exports.parse(v)); + this._setProperty( + "font-size", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/fontStyle.js b/lib/properties/fontStyle.js index 5f45ddb2..e83a5df7 100644 --- a/lib/properties/fontStyle.js +++ b/lib/properties/fontStyle.js @@ -1,13 +1,36 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.isValidPropertyValue("font-style", v)) { - return strings.asciiLowercase(v); + } + const value = parsers.parsePropertyValue("font-style", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length) { + if (value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: + } + } else if (value.length === 2) { + const [part1, part2] = value; + const val1 = part1.type === "Identifier" && part1.name; + const val2 = parsers.parseAngle([part2]); + if (val1 && val1 === "oblique" && val2) { + return `${val1} ${val2}`; + } + } + } else if (typeof value === "string") { + return value; } }; @@ -18,7 +41,12 @@ module.exports.definition = { this._setProperty("font", ""); this._setProperty("font-style", v); } else { - this._setProperty("font-style", module.exports.parse(v)); + this._setProperty( + "font-style", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/fontVariant.js b/lib/properties/fontVariant.js index 4a12c376..b045e119 100644 --- a/lib/properties/fontVariant.js +++ b/lib/properties/fontVariant.js @@ -1,15 +1,46 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; } - if (parsers.isValidPropertyValue("font-variant", v)) { - const val = strings.asciiLowercase(v); - return parsers.splitValue(val).join(" "); + const values = parsers.splitValue(v); + const parsedValues = []; + for (const val of values) { + const value = parsers.parsePropertyValue("font-variant", val, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type, value: itemValue }] = value; + switch (type) { + case "Function": { + parsedValues.push(`${name}(${itemValue})`); + break; + } + case "GlobalKeyword": + case "Identifier": { + parsedValues.push(name); + break; + } + default: { + return; + } + } + } else if (typeof value === "string") { + parsedValues.push(value); + } + } + if (parsedValues.length) { + if (parsedValues.length > 1) { + if (parsedValues.includes("normal") || parsedValues.includes("none")) { + return; + } + } + return parsedValues.join(" "); } }; @@ -20,7 +51,12 @@ module.exports.definition = { this._setProperty("font", ""); this._setProperty("font-valiant", v); } else { - this._setProperty("font-variant", module.exports.parse(v)); + this._setProperty( + "font-variant", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/fontWeight.js b/lib/properties/fontWeight.js index cb17034b..89bc0ba8 100644 --- a/lib/properties/fontWeight.js +++ b/lib/properties/fontWeight.js @@ -1,23 +1,41 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("font-weight", v)) { - const val = parsers.parseNumber(v, true); - if (val) { - if (val < 1 || parseFloat(val) > 1000) { - return; + const value = parsers.parsePropertyValue("font-weight", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, raw, type }] = value; + switch (type) { + case "Calc": { + if (isNumber) { + return raw; + } + break; + } + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: { + const parsedValue = parsers.parseNumber(value, { + min: 1, + max: 1000 + }); + if (parsedValue) { + return parsedValue; + } } - return val; } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; @@ -28,7 +46,12 @@ module.exports.definition = { this._setProperty("font", ""); this._setProperty("font-weight", v); } else { - this._setProperty("font-weight", module.exports.parse(v)); + this._setProperty( + "font-weight", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/lineHeight.js b/lib/properties/lineHeight.js index bff040a8..1c717085 100644 --- a/lib/properties/lineHeight.js +++ b/lib/properties/lineHeight.js @@ -1,20 +1,39 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("line-height", v)) { - const val = parsers.parseNumber(v, true) ?? parsers.parseMeasurement(v, true); - if (val) { - return val; + const value = parsers.parsePropertyValue("line-height", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, raw, type }] = value; + switch (type) { + case "Calc": { + return raw; + } + case "GlobalKeyword": + case "Identifier": { + return name; + } + case "Number": { + return parsers.parseNumber(value, { + min: 0 + }); + } + default: { + return parsers.parseMeasurement(value, { + min: 0 + }); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; @@ -25,7 +44,12 @@ module.exports.definition = { this._setProperty("font", ""); this._setProperty("line-height", v); } else { - this._setProperty("line-height", module.exports.parse(v)); + this._setProperty( + "line-height", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/test/properties.test.js b/test/properties.test.js index 9cfe98a6..3d2f58d9 100644 --- a/test/properties.test.js +++ b/test/properties.test.js @@ -1748,10 +1748,79 @@ describe("font", () => { testPropertyValue("font-style", "italic", "italic"); }); + it("font-variant should set / get keyword", () => { + testPropertyValue("font-variant", "normal", "normal"); + }); + + it("font-variant should set / get keyword", () => { + testPropertyValue("font-variant", "none", "none"); + }); + + it("font-variant should set / get keyword", () => { + testPropertyValue("font-variant", "none", "none"); + }); + + it("font-variant should set / get keyword", () => { + testPropertyValue("font-variant", "common-ligatures", "common-ligatures"); + }); + + it("font-variant should set / get keyword", () => { + testPropertyValue("font-variant", "no-common-ligatures", "no-common-ligatures"); + }); + it("font-variant should set / get keyword", () => { testPropertyValue("font-variant", "small-caps", "small-caps"); }); + it("font-variant should set / get keyword", () => { + testPropertyValue("font-variant", "stylistic(flowing)", "stylistic(flowing)"); + }); + + it("font-variant should set / get keyword", () => { + testPropertyValue( + "font-variant", + "stylistic(flowing) historical-forms styleset(flowing) character-variant(flowing) swash(flowing) ornaments(flowing) annotation(flowing)", + "stylistic(flowing) historical-forms styleset(flowing) character-variant(flowing) swash(flowing) ornaments(flowing) annotation(flowing)" + ); + }); + + // FIXME: + it.skip("font-variant should set / get keyword", () => { + testPropertyValue( + "font-variant", + "annotation(flowing) ornaments(flowing) swash(flowing) character-variant(flowing) styleset(flowing) historical-forms stylistic(flowing)", + "stylistic(flowing) historical-forms styleset(flowing) character-variant(flowing) swash(flowing) ornaments(flowing) annotation(flowing)" + ); + }); + + it("font-variant should set / get keyword", () => { + testPropertyValue("font-variant", "jis78", "jis78"); + }); + + it("font-variant should set / get keyword", () => { + testPropertyValue("font-variant", "ruby", "ruby"); + }); + + it("font-variant should set / get keyword", () => { + testPropertyValue("font-variant", "sub", "sub"); + }); + + it("font-variant should set / get keyword", () => { + testPropertyValue("font-variant", "super", "super"); + }); + + it.skip("font-variant should not set / get invalid keywords", () => { + testPropertyValue("font-variant", "normal none", ""); + }); + + it.skip("font-variant should not set / get invalid keywords", () => { + testPropertyValue("font-variant", "normal small-caps", ""); + }); + + it.skip("font-variant should not set / get invalid keywords", () => { + testPropertyValue("font-variant", "none small-caps", ""); + }); + it("font-weight should set / get keyword", () => { testPropertyValue("font-weight", "bold", "bold"); }); @@ -1824,6 +1893,10 @@ describe("font", () => { testPropertyValue("font-family", "sans-serif", "sans-serif"); }); + it("font-family should set / get keyword", () => { + testPropertyValue("font-family", '"sans-serif"', '"sans-serif"'); + }); + it("font-family should set / get family name", () => { testPropertyValue("font-family", "Times", "Times"); }); @@ -1936,14 +2009,14 @@ describe("font", () => { testImplicitPropertyValue( "font", "normal medium Gill Sans Extrabold, sans-serif", - "medium Gill Sans Extrabold, sans-serif", + 'medium "Gill Sans Extrabold", sans-serif', new Map([ ["font-style", "normal"], ["font-variant", "normal"], ["font-weight", "normal"], ["font-size", "medium"], ["line-height", "normal"], - ["font-family", "Gill Sans Extrabold, sans-serif"] + ["font-family", '"Gill Sans Extrabold", sans-serif'] ]) ); }); @@ -2155,6 +2228,22 @@ describe("font", () => { ]) ); }); + + it("font shorthand should set / get values", () => { + testImplicitPropertyValue( + "font", + "initial", + "initial", + new Map([ + ["font-style", "initial"], + ["font-variant", "initial"], + ["font-weight", "initial"], + ["font-size", "initial"], + ["line-height", "initial"], + ["font-family", "initial"] + ]) + ); + }); }); describe("logical", () => { From a7668822eb6a12928e0b17d33044e43c9f9fd9e5 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Sun, 10 Aug 2025 21:23:07 +0900 Subject: [PATCH 35/41] Fix calc and shorthand handlers --- lib/CSSStyleDeclaration.js | 92 ++++++++++++------------ lib/parsers.js | 119 ++++++++++++++----------------- lib/properties/backgroundSize.js | 8 +-- lib/properties/flex.js | 16 ++--- lib/properties/flexBasis.js | 8 +-- lib/properties/flexGrow.js | 4 +- lib/properties/flexShrink.js | 4 +- lib/properties/fontSize.js | 8 +-- lib/properties/fontWeight.js | 4 +- lib/properties/height.js | 8 +-- lib/properties/left.js | 8 +-- lib/properties/lineHeight.js | 4 +- lib/properties/margin.js | 51 +++++++++---- lib/properties/marginBottom.js | 8 +-- lib/properties/marginLeft.js | 8 +-- lib/properties/marginRight.js | 8 +-- lib/properties/marginTop.js | 8 +-- lib/properties/opacity.js | 4 +- lib/properties/padding.js | 50 ++++++++----- lib/properties/paddingBottom.js | 8 +-- lib/properties/paddingLeft.js | 8 +-- lib/properties/paddingRight.js | 8 +-- lib/properties/paddingTop.js | 8 +-- lib/properties/right.js | 8 +-- lib/properties/top.js | 8 +-- lib/properties/width.js | 8 +-- test/CSSStyleDeclaration.test.js | 33 ++++++--- test/parsers.test.js | 63 +++++++++++----- test/properties.test.js | 18 ++--- 29 files changed, 323 insertions(+), 267 deletions(-) diff --git a/lib/CSSStyleDeclaration.js b/lib/CSSStyleDeclaration.js index b5225c6d..70cff953 100644 --- a/lib/CSSStyleDeclaration.js +++ b/lib/CSSStyleDeclaration.js @@ -10,7 +10,6 @@ const implementedProperties = require("./generated/implementedProperties"); const generatedProperties = require("./generated/properties"); const { hasVarFunc, - isValidPropertyValue, parseCSS, parsePropertyValue, parseShorthand, @@ -139,9 +138,9 @@ class CSSStyleDeclaration { } else { if (shorthandProperties.has(property)) { const longhandProperties = shorthandProperties.get(property); - for (const [longhandProperty] of longhandProperties) { - if (properties.has(longhandProperty) && !this.getPropertyPriority(longhandProperty)) { - properties.delete(longhandProperty); + for (const [longhand] of longhandProperties) { + if (properties.has(longhand) && !this.getPropertyPriority(longhand)) { + properties.delete(longhand); } } } @@ -151,7 +150,7 @@ class CSSStyleDeclaration { return [...properties.values()].join(" "); } - set cssText(value) { + set cssText(val) { if (this._readonly) { const msg = "cssText can not be modified."; const name = "NoModificationAllowedError"; @@ -166,7 +165,7 @@ class CSSStyleDeclaration { this._setInProgress = true; try { const valueObj = parseCSS( - value, + val, { context: "declarationList", parseValue: false @@ -178,17 +177,21 @@ class CSSStyleDeclaration { const { important, property, - value: { value: rawValue } + value: { value } } = item; + const priority = important ? "important" : ""; const isCustomProperty = property.startsWith("--"); - if ( - isCustomProperty || - hasVarFunc(rawValue) || - isValidPropertyValue(property, rawValue) - ) { - this.setProperty(property, rawValue, important ? "important" : ""); + if (isCustomProperty || hasVarFunc(value)) { + this.setProperty(property, value, priority); } else { - this.removeProperty(property); + const parsedValue = parsePropertyValue(property, value, { + globalObject: this._global + }); + if (parsedValue) { + this.setProperty(property, parsedValue, priority); + } else { + this.removeProperty(property); + } } } } @@ -329,18 +332,18 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { * @param {object} shorthandFor */ value(property, shorthandFor) { - const parts = []; + const values = []; for (const key of shorthandFor.keys()) { const val = this.getPropertyValue(key); if (hasVarFunc(val)) { return ""; } if (val !== "") { - parts.push(val); + values.push(val); } } - if (parts.length) { - return parts.join(" "); + if (values.length) { + return values.join(" "); } if (this._values.has(property)) { return this.getPropertyValue(property); @@ -356,20 +359,20 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { * @param {Array.} positions */ value(property, positions = []) { - const parts = []; + const values = []; for (const position of positions) { const val = this.getPropertyValue(`${property}-${position}`); if (val === "" || hasVarFunc(val)) { return ""; } - parts.push(val); + values.push(val); } - if (!parts.length) { + if (!values.length) { return ""; } switch (positions.length) { case 4: { - const [top, right, bottom, left] = parts; + const [top, right, bottom, left] = values; if (top === right && top === bottom && right === left) { return top; } @@ -382,7 +385,7 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { return `${top} ${right} ${bottom} ${left}`; } case 2: { - const [x, y] = parts; + const [x, y] = values; if (x === y) { return x; } @@ -521,40 +524,35 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { value(prefix, part, val, parser, positions = []) { const suffix = part ? `-${part}` : ""; const shorthandProp = `${prefix}${suffix}`; - const parts = []; + const values = []; if (val === "") { - parts.push(val); + values.push(val); } else { - const value = parsePropertyValue(shorthandProp, val, { + const parsedValue = parser(val, { globalObject: this._global }); - if (typeof value !== "string") { + if (typeof parsedValue !== "string") { return; } - parts.push(...splitValue(val)); + values.push(...splitValue(parsedValue)); } - if (!parts.length || parts.length > positions.length) { + if (!values.length || values.length > positions.length) { return; } - const properties = parts.map((p) => - parser(p, { - globalObject: this._global - }) - ); - this._setProperty(shorthandProp, properties.join(" ")); + this._setProperty(shorthandProp, values.join(" ")); switch (positions.length) { case 4: - if (properties.length === 1) { - properties.push(properties[0], properties[0], properties[0]); - } else if (properties.length === 2) { - properties.push(properties[0], properties[1]); - } else if (properties.length === 3) { - properties.push(properties[1]); + if (values.length === 1) { + values.push(values[0], values[0], values[0]); + } else if (values.length === 2) { + values.push(values[0], values[1]); + } else if (values.length === 3) { + values.push(values[1]); } break; case 2: - if (properties.length === 1) { - properties.push(properties[0]); + if (values.length === 1) { + values.push(values[0]); } break; default: @@ -562,7 +560,7 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { for (let i = 0; i < positions.length; i++) { const property = `${prefix}-${positions[i]}${suffix}`; this.removeProperty(property); - this._values.set(property, properties[i]); + this._values.set(property, values[i]); } }, enumerable: false @@ -581,14 +579,14 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { * @param {Array.} positions */ value(prefix, part, val, parser, positions = []) { - const value = parser(val, { + const parsedValue = parser(val, { globalObject: this._global }); - if (typeof value !== "string") { + if (typeof parsedValue !== "string") { return; } const property = `${prefix}-${part}`; - this._setProperty(property, value); + this._setProperty(property, parsedValue); const combinedPriority = this.getPropertyPriority(prefix); const subparts = []; for (const position of positions) { diff --git a/lib/parsers.js b/lib/parsers.js index 9b25f9be..6e97e274 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -727,28 +727,25 @@ exports.parsePropertyValue = function parsePropertyValue(prop, val, opt = {}) { } val = parsedValue; } - let value = val; - if (!caseSensitive) { - value = asciiLowercase(val); - } - if (GLOBAL_KEY.includes(value)) { + const lowerCasedValue = asciiLowercase(val); + if (GLOBAL_KEY.includes(lowerCasedValue)) { if (inArray) { return [ { type: "GlobalKeyword", - name: value + name: lowerCasedValue } ]; } - return value; - } else if (SYS_COLOR.includes(value)) { + return lowerCasedValue; + } else if (SYS_COLOR.includes(lowerCasedValue)) { if (/^(?:-webkit-)?(?:[a-z][a-z\d]*-)*color$/i.test(prop)) { - return value; + return lowerCasedValue; } return; } try { - const ast = exports.parseCSS(value, { + const ast = exports.parseCSS(val, { context: "value" }); const { error, matched } = cssTree.lexer.matchProperty(prop, ast); @@ -758,56 +755,24 @@ exports.parsePropertyValue = function parsePropertyValue(prop, val, opt = {}) { if (inArray) { const obj = cssTree.toPlainObject(ast); const items = obj.children; - if (items.length === 1) { - const [{ children, name, type }] = items; - if (type === "Function") { - const itemValue = value - .replace(new RegExp(`^${name}\\(`), "") - .replace(/\)$/, "") - .trim(); - if (name === "calc") { - if (children.length === 1) { - const [child] = children; - if (child.type === "Number") { - return [ - { - type: "Calc", - name: "calc", - isNumber: true, - value: `${parseFloat(child.value)}`, - raw: val - } - ]; - } - } - return [ - { - type: "Calc", - name: "calc", - isNumber: false, - value: itemValue, - raw: val - } - ]; - } - return [ - { + const parsedValues = []; + for (const item of items) { + const { children, name, type, value, unit } = item; + switch (type) { + case "Dimension": { + parsedValues.push({ type, - name, - value: itemValue, - raw: val - } - ]; - } - } else if (items.length > 1) { - const arr = []; - for (const item of items) { - const { children, name, type } = item; - if (type === "Function") { - const raw = cssTree + value, + unit: asciiLowercase(unit) + }); + break; + } + case "Function": { + const css = cssTree .generate(item) .replace(/\)(?!\)|\s|,)/g, ") ") .trim(); + const raw = items.length === 1 ? val : css; const itemValue = raw .replace(new RegExp(`^${name}\\(`), "") .replace(/\)$/, "") @@ -816,41 +781,61 @@ exports.parsePropertyValue = function parsePropertyValue(prop, val, opt = {}) { if (children.length === 1) { const [child] = children; if (child.type === "Number") { - arr.push({ + parsedValues.push({ type: "Calc", name: "calc", isNumber: true, value: `${parseFloat(child.value)}`, raw }); + } else { + parsedValues.push({ + type: "Calc", + name: "calc", + isNumber: false, + value: `${asciiLowercase(itemValue)}`, + raw + }); } } else { - arr.push({ + parsedValues.push({ type: "Calc", name: "calc", isNumber: false, - value: itemValue, + value: asciiLowercase(itemValue), raw }); } } else { - arr.push({ + parsedValues.push({ type, name, - raw, - value: itemValue + value: asciiLowercase(itemValue), + raw }); } - } else { - arr.push(item); + break; + } + case "Identifier": { + if (caseSensitive) { + parsedValues.push(item); + } else { + parsedValues.push({ + type, + name: asciiLowercase(name) + }); + } + break; + } + default: { + parsedValues.push(item); } } - return arr; } - return items; + return parsedValues; } } catch { return; } - return value; + return val; }; diff --git a/lib/properties/backgroundSize.js b/lib/properties/backgroundSize.js index 0a1e315d..9cd8e2ae 100644 --- a/lib/properties/backgroundSize.js +++ b/lib/properties/backgroundSize.js @@ -18,13 +18,13 @@ module.exports.parse = function parse(v, opt = {}) { }); if (Array.isArray(value) && value.length) { if (value.length === 1) { - const [{ isNumber, name, raw, type }] = value; + const [{ isNumber, name, type, value: itemValue }] = value; switch (type) { case "Calc": { if (isNumber) { return; } - parsedValues.push(raw); + parsedValues.push(`${name}(${itemValue})`); break; } case "GlobalKeyword": @@ -44,7 +44,7 @@ module.exports.parse = function parse(v, opt = {}) { const [val1, val2] = value; const parts = []; if (val1.type === "Calc" && !val1.isNumber) { - parts.push(val1.raw); + parts.push(`${val1.name}(${val1.value})`); } else if (val1.type === "Identifier") { parts.push(val1.name); } else if (val1.type === "Dimension") { @@ -59,7 +59,7 @@ module.exports.parse = function parse(v, opt = {}) { if (val2.isNumber) { return; } - parts.push(val2.raw); + parts.push(`${val2.name}(${val2.value})`); break; } case "Dimension": { diff --git a/lib/properties/flex.js b/lib/properties/flex.js index a86710bf..6a93c26f 100644 --- a/lib/properties/flex.js +++ b/lib/properties/flex.js @@ -22,13 +22,13 @@ module.exports.parse = function parse(v, opt = {}) { }); if (Array.isArray(value) && value.length) { if (value.length === 1) { - const [{ isNumber, name, raw, type, unit, value: itemValue }] = value; + const [{ isNumber, name, type, unit, value: itemValue }] = value; switch (type) { case "Calc": { if (isNumber) { - return `${raw} 1 0%`; + return `${name}(${itemValue}) 1 0%`; } - return `1 1 ${raw}`; + return `1 1 ${name}(${itemValue})`; } case "Dimension": { return `1 1 ${itemValue}${unit}`; @@ -60,7 +60,7 @@ module.exports.parse = function parse(v, opt = {}) { }; const [val1, val2, val3] = value; if (val1.type === "Calc" && val1.isNumber) { - flex["flex-grow"] = val1.raw; + flex["flex-grow"] = `${val1.name}(${val1.value})`; } else if (val1.type === "Number") { flex["flex-grow"] = val1.value; } else { @@ -68,14 +68,14 @@ module.exports.parse = function parse(v, opt = {}) { } if (val3) { if (val2.type === "Calc" && val2.isNumber) { - flex["flex-shrink"] = val2.raw; + flex["flex-shrink"] = `${val2.name}(${val2.value})`; } else if (val2.type === "Number") { flex["flex-shrink"] = val2.value; } else { return; } if (val3.type === "Calc" && !val3.isNumber) { - flex["flex-basis"] = val3.raw; + flex["flex-basis"] = `${val3.name}(${val3.value})`; } else if (val3.type === "Dimension") { flex["flex-basis"] = `${val3.value}${val3.unit}`; } else if (val3.type === "Percentage") { @@ -87,9 +87,9 @@ module.exports.parse = function parse(v, opt = {}) { switch (val2.type) { case "Calc": { if (val2.isNumber) { - flex["flex-shrink"] = val2.raw; + flex["flex-shrink"] = `${val2.name}(${val2.value})`; } else { - flex["flex-basis"] = val2.raw; + flex["flex-basis"] = `${val2.name}(${val2.value})`; } break; } diff --git a/lib/properties/flexBasis.js b/lib/properties/flexBasis.js index 84f51705..08d857ce 100644 --- a/lib/properties/flexBasis.js +++ b/lib/properties/flexBasis.js @@ -12,13 +12,13 @@ module.exports.parse = function parse(v, opt = {}) { inArray: true }); if (Array.isArray(value) && value.length === 1) { - const [{ isNumber, name, raw, type }] = value; + const [{ isNumber, name, type, value: itemValue }] = value; switch (type) { case "Calc": { - if (!isNumber) { - return raw; + if (isNumber) { + return; } - break; + return `${name}(${itemValue})`; } case "GlobalKeyword": case "Identifier": { diff --git a/lib/properties/flexGrow.js b/lib/properties/flexGrow.js index ddd334c3..ccaa7852 100644 --- a/lib/properties/flexGrow.js +++ b/lib/properties/flexGrow.js @@ -12,11 +12,11 @@ module.exports.parse = function parse(v, opt = {}) { inArray: true }); if (Array.isArray(value) && value.length === 1) { - const [{ isNumber, name, raw, type }] = value; + const [{ isNumber, name, type, value: itemValue }] = value; switch (type) { case "Calc": { if (isNumber) { - return raw; + return `${name}(${itemValue})`; } break; } diff --git a/lib/properties/flexShrink.js b/lib/properties/flexShrink.js index cff60a04..b4c82312 100644 --- a/lib/properties/flexShrink.js +++ b/lib/properties/flexShrink.js @@ -12,11 +12,11 @@ module.exports.parse = function parse(v, opt = {}) { inArray: true }); if (Array.isArray(value) && value.length === 1) { - const [{ isNumber, name, raw, type }] = value; + const [{ isNumber, name, type, value: itemValue }] = value; switch (type) { case "Calc": { if (isNumber) { - return raw; + return `${name}(${itemValue})`; } break; } diff --git a/lib/properties/fontSize.js b/lib/properties/fontSize.js index c3df05de..a8f9dae2 100644 --- a/lib/properties/fontSize.js +++ b/lib/properties/fontSize.js @@ -12,13 +12,13 @@ module.exports.parse = function parse(v, opt = {}) { inArray: true }); if (Array.isArray(value) && value.length === 1) { - const [{ isNumber, name, raw, type }] = value; + const [{ isNumber, name, type, value: itemValue }] = value; switch (type) { case "Calc": { - if (!isNumber) { - return raw; + if (isNumber) { + return; } - break; + return `${name}(${itemValue})`; } case "GlobalKeyword": case "Identifier": { diff --git a/lib/properties/fontWeight.js b/lib/properties/fontWeight.js index 89bc0ba8..e43b76b2 100644 --- a/lib/properties/fontWeight.js +++ b/lib/properties/fontWeight.js @@ -12,11 +12,11 @@ module.exports.parse = function parse(v, opt = {}) { inArray: true }); if (Array.isArray(value) && value.length === 1) { - const [{ isNumber, name, raw, type }] = value; + const [{ isNumber, name, type, value: itemValue }] = value; switch (type) { case "Calc": { if (isNumber) { - return raw; + return `${name}(${itemValue})`; } break; } diff --git a/lib/properties/height.js b/lib/properties/height.js index 13f2ed19..34f7dd95 100644 --- a/lib/properties/height.js +++ b/lib/properties/height.js @@ -12,13 +12,13 @@ module.exports.parse = function parse(v, opt = {}) { inArray: true }); if (Array.isArray(value) && value.length === 1) { - const [{ isNumber, name, raw, type }] = value; + const [{ isNumber, name, type, value: itemValue }] = value; switch (type) { case "Calc": { - if (!isNumber) { - return raw; + if (isNumber) { + return; } - break; + return `${name}(${itemValue})`; } case "GlobalKeyword": case "Identifier": { diff --git a/lib/properties/left.js b/lib/properties/left.js index 904f01f2..741f9d2a 100644 --- a/lib/properties/left.js +++ b/lib/properties/left.js @@ -12,13 +12,13 @@ module.exports.parse = function parse(v, opt = {}) { inArray: true }); if (Array.isArray(value) && value.length === 1) { - const [{ isNumber, name, raw, type }] = value; + const [{ isNumber, name, type, value: itemValue }] = value; switch (type) { case "Calc": { - if (!isNumber) { - return raw; + if (isNumber) { + return; } - break; + return `${name}(${itemValue})`; } case "GlobalKeyword": case "Identifier": { diff --git a/lib/properties/lineHeight.js b/lib/properties/lineHeight.js index 1c717085..bd4dabc2 100644 --- a/lib/properties/lineHeight.js +++ b/lib/properties/lineHeight.js @@ -12,10 +12,10 @@ module.exports.parse = function parse(v, opt = {}) { inArray: true }); if (Array.isArray(value) && value.length === 1) { - const [{ name, raw, type }] = value; + const [{ name, type, value: itemValue }] = value; switch (type) { case "Calc": { - return raw; + return `${name}(${itemValue})`; } case "GlobalKeyword": case "Identifier": { diff --git a/lib/properties/margin.js b/lib/properties/margin.js index e3881cb4..e66043a9 100644 --- a/lib/properties/margin.js +++ b/lib/properties/margin.js @@ -13,25 +13,46 @@ module.exports.parse = function parse(v, opt = {}) { globalObject, inArray: true }); - if (Array.isArray(value) && value.length === 1) { - const [{ isNumber, name, raw, type }] = value; - switch (type) { - case "Calc": { - if (!isNumber) { - return raw; + const parsedValues = []; + if (Array.isArray(value) && value.length) { + if (value.length > 4) { + return; + } + for (let i = 0; i < value.length; i++) { + const { isNumber, name, type, value: itemValue } = value[i]; + switch (type) { + case "Calc": { + if (isNumber) { + return; + } + parsedValues.push(`${name}(${itemValue})`); + break; + } + case "GlobalKeyword": { + if (i !== 0) { + return; + } + parsedValues.push(name); + break; + } + case "Identifier": { + parsedValues.push(name); + break; + } + default: { + const parsedValue = parsers.parseMeasurement([value[i]]); + if (!parsedValue) { + return; + } + parsedValues.push(parsedValue); } - break; - } - case "GlobalKeyword": - case "Identifier": { - return name; - } - default: { - return parsers.parseMeasurement(value); } } } else if (typeof value === "string") { - return value; + parsedValues.push(value); + } + if (parsedValues.length) { + return parsedValues.join(" "); } }; diff --git a/lib/properties/marginBottom.js b/lib/properties/marginBottom.js index 316927ed..6ca95818 100644 --- a/lib/properties/marginBottom.js +++ b/lib/properties/marginBottom.js @@ -14,13 +14,13 @@ module.exports.parse = function parse(v, opt = {}) { inArray: true }); if (Array.isArray(value) && value.length === 1) { - const [{ isNumber, name, raw, type }] = value; + const [{ isNumber, name, type, value: itemValue }] = value; switch (type) { case "Calc": { - if (!isNumber) { - return raw; + if (isNumber) { + return; } - break; + return `${name}(${itemValue})`; } case "GlobalKeyword": case "Identifier": { diff --git a/lib/properties/marginLeft.js b/lib/properties/marginLeft.js index b7363cad..24957661 100644 --- a/lib/properties/marginLeft.js +++ b/lib/properties/marginLeft.js @@ -14,13 +14,13 @@ module.exports.parse = function parse(v, opt = {}) { inArray: true }); if (Array.isArray(value) && value.length === 1) { - const [{ isNumber, name, raw, type }] = value; + const [{ isNumber, name, type, value: itemValue }] = value; switch (type) { case "Calc": { - if (!isNumber) { - return raw; + if (isNumber) { + return; } - break; + return `${name}(${itemValue})`; } case "GlobalKeyword": case "Identifier": { diff --git a/lib/properties/marginRight.js b/lib/properties/marginRight.js index 571f6e2d..b98825ab 100644 --- a/lib/properties/marginRight.js +++ b/lib/properties/marginRight.js @@ -14,13 +14,13 @@ module.exports.parse = function parse(v, opt = {}) { inArray: true }); if (Array.isArray(value) && value.length === 1) { - const [{ isNumber, name, raw, type }] = value; + const [{ isNumber, name, type, value: itemValue }] = value; switch (type) { case "Calc": { - if (!isNumber) { - return raw; + if (isNumber) { + return; } - break; + return `${name}(${itemValue})`; } case "GlobalKeyword": case "Identifier": { diff --git a/lib/properties/marginTop.js b/lib/properties/marginTop.js index 1568e1b7..0f4802e1 100644 --- a/lib/properties/marginTop.js +++ b/lib/properties/marginTop.js @@ -14,13 +14,13 @@ module.exports.parse = function parse(v, opt = {}) { inArray: true }); if (Array.isArray(value) && value.length === 1) { - const [{ isNumber, name, raw, type }] = value; + const [{ isNumber, name, type, value: itemValue }] = value; switch (type) { case "Calc": { - if (!isNumber) { - return raw; + if (isNumber) { + return; } - break; + return `${name}(${itemValue})`; } case "GlobalKeyword": case "Identifier": { diff --git a/lib/properties/opacity.js b/lib/properties/opacity.js index aea7d5d9..6a6955b2 100644 --- a/lib/properties/opacity.js +++ b/lib/properties/opacity.js @@ -12,10 +12,10 @@ module.exports.parse = function parse(v, opt = {}) { inArray: true }); if (Array.isArray(value) && value.length === 1) { - const [{ name, raw, type }] = value; + const [{ name, type, value: itemValue }] = value; switch (type) { case "Calc": { - return raw; + return `${name}(${itemValue})`; } case "GlobalKeyword": case "Identifier": { diff --git a/lib/properties/padding.js b/lib/properties/padding.js index 9875a3a7..401f7f23 100644 --- a/lib/properties/padding.js +++ b/lib/properties/padding.js @@ -13,26 +13,44 @@ module.exports.parse = function parse(v, opt = {}) { globalObject, inArray: true }); - if (Array.isArray(value) && value.length === 1) { - const [{ isNumber, name, raw, type }] = value; - switch (type) { - case "Calc": { - if (!isNumber) { - return raw; + const parsedValues = []; + if (Array.isArray(value) && value.length) { + if (value.length > 4) { + return; + } + for (let i = 0; i < value.length; i++) { + const { isNumber, name, type, value: itemValue } = value[i]; + switch (type) { + case "Calc": { + if (isNumber) { + return; + } + parsedValues.push(`${name}(${itemValue})`); + break; + } + case "GlobalKeyword": { + if (i !== 0) { + return; + } + parsedValues.push(name); + break; + } + default: { + const parsedValue = parsers.parseMeasurement([value[i]], { + min: 0 + }); + if (!parsedValue) { + return; + } + parsedValues.push(parsedValue); } - break; - } - case "GlobalKeyword": { - return name; - } - default: { - return parsers.parseMeasurement(value, { - min: 0 - }); } } } else if (typeof value === "string") { - return value; + parsedValues.push(value); + } + if (parsedValues.length) { + return parsedValues.join(" "); } }; diff --git a/lib/properties/paddingBottom.js b/lib/properties/paddingBottom.js index e2d73aff..fe68c04f 100644 --- a/lib/properties/paddingBottom.js +++ b/lib/properties/paddingBottom.js @@ -14,13 +14,13 @@ module.exports.parse = function parse(v, opt = {}) { inArray: true }); if (Array.isArray(value) && value.length === 1) { - const [{ isNumber, name, raw, type }] = value; + const [{ isNumber, name, type, value: itemValue }] = value; switch (type) { case "Calc": { - if (!isNumber) { - return raw; + if (isNumber) { + return; } - break; + return `${name}(${itemValue})`; } case "GlobalKeyword": { return name; diff --git a/lib/properties/paddingLeft.js b/lib/properties/paddingLeft.js index 0f8874b9..857afe53 100644 --- a/lib/properties/paddingLeft.js +++ b/lib/properties/paddingLeft.js @@ -14,13 +14,13 @@ module.exports.parse = function parse(v, opt = {}) { inArray: true }); if (Array.isArray(value) && value.length === 1) { - const [{ isNumber, name, raw, type }] = value; + const [{ isNumber, name, type, value: itemValue }] = value; switch (type) { case "Calc": { - if (!isNumber) { - return raw; + if (isNumber) { + return; } - break; + return `${name}(${itemValue})`; } case "GlobalKeyword": { return name; diff --git a/lib/properties/paddingRight.js b/lib/properties/paddingRight.js index 1521e21a..dc59d635 100644 --- a/lib/properties/paddingRight.js +++ b/lib/properties/paddingRight.js @@ -14,13 +14,13 @@ module.exports.parse = function parse(v, opt = {}) { inArray: true }); if (Array.isArray(value) && value.length === 1) { - const [{ isNumber, name, raw, type }] = value; + const [{ isNumber, name, type, value: itemValue }] = value; switch (type) { case "Calc": { - if (!isNumber) { - return raw; + if (isNumber) { + return; } - break; + return `${name}(${itemValue})`; } case "GlobalKeyword": { return name; diff --git a/lib/properties/paddingTop.js b/lib/properties/paddingTop.js index 641d1843..1343d4ca 100644 --- a/lib/properties/paddingTop.js +++ b/lib/properties/paddingTop.js @@ -14,13 +14,13 @@ module.exports.parse = function parse(v, opt = {}) { inArray: true }); if (Array.isArray(value) && value.length === 1) { - const [{ isNumber, name, raw, type }] = value; + const [{ isNumber, name, type, value: itemValue }] = value; switch (type) { case "Calc": { - if (!isNumber) { - return raw; + if (isNumber) { + return; } - break; + return `${name}(${itemValue})`; } case "GlobalKeyword": { return name; diff --git a/lib/properties/right.js b/lib/properties/right.js index d0e823c6..1ede88d9 100644 --- a/lib/properties/right.js +++ b/lib/properties/right.js @@ -12,13 +12,13 @@ module.exports.parse = function parse(v, opt = {}) { inArray: true }); if (Array.isArray(value) && value.length === 1) { - const [{ isNumber, name, raw, type }] = value; + const [{ isNumber, name, type, value: itemValue }] = value; switch (type) { case "Calc": { - if (!isNumber) { - return raw; + if (isNumber) { + return; } - break; + return `${name}(${itemValue})`; } case "GlobalKeyword": case "Identifier": { diff --git a/lib/properties/top.js b/lib/properties/top.js index 1c709a9f..42d53a53 100644 --- a/lib/properties/top.js +++ b/lib/properties/top.js @@ -12,13 +12,13 @@ module.exports.parse = function parse(v, opt = {}) { inArray: true }); if (Array.isArray(value) && value.length === 1) { - const [{ isNumber, name, raw, type }] = value; + const [{ isNumber, name, type, value: itemValue }] = value; switch (type) { case "Calc": { - if (!isNumber) { - return raw; + if (isNumber) { + return; } - break; + return `${name}(${itemValue})`; } case "GlobalKeyword": case "Identifier": { diff --git a/lib/properties/width.js b/lib/properties/width.js index d2389c61..5848c644 100644 --- a/lib/properties/width.js +++ b/lib/properties/width.js @@ -12,13 +12,13 @@ module.exports.parse = function parse(v, opt = {}) { inArray: true }); if (Array.isArray(value) && value.length === 1) { - const [{ isNumber, name, raw, type }] = value; + const [{ isNumber, name, type, value: itemValue }] = value; switch (type) { case "Calc": { - if (!isNumber) { - return raw; + if (isNumber) { + return; } - break; + return `${name}(${itemValue})`; } case "GlobalKeyword": case "Identifier": { diff --git a/test/CSSStyleDeclaration.test.js b/test/CSSStyleDeclaration.test.js index e24d6b1f..9f4639ca 100644 --- a/test/CSSStyleDeclaration.test.js +++ b/test/CSSStyleDeclaration.test.js @@ -393,25 +393,40 @@ describe("CSSStyleDeclaration", () => { assert.strictEqual(style.cssText, ""); }); - it('setting border values to "none" should clear dependent values', () => { + // FIXME: Not sure what the expected results should be. + it('setting border values to "none" should not clear dependent values', () => { const style = new CSSStyleDeclaration(); style.borderTopWidth = "1px"; assert.strictEqual(style.cssText, "border-top-width: 1px;"); style.border = "none"; - assert.strictEqual(style.cssText, ""); + // Firefox: "border: medium;", Chrome: "border: none;". + assert.strictEqual(style.cssText, "border-top-width: 1px; border: none;"); + assert.strictEqual(style.borderTopStyle, "none"); + assert.strictEqual(style.borderTopWidth, "1px"); + + style.border = null; style.borderTopWidth = "1px"; assert.strictEqual(style.cssText, "border-top-width: 1px;"); - style.borderTopStyle = "none"; - assert.strictEqual(style.cssText, ""); + style.borderStyle = "none"; + assert.strictEqual(style.cssText, "border-top-width: 1px; border-style: none;"); + assert.strictEqual(style.borderTopStyle, "none"); + assert.strictEqual(style.borderTopWidth, "1px"); + style.border = null; + style.borderTopWidth = "1px"; assert.strictEqual(style.cssText, "border-top-width: 1px;"); style.borderTop = "none"; - assert.strictEqual(style.cssText, ""); + assert.strictEqual(style.cssText, "border-top: none;"); + assert.strictEqual(style.borderTopStyle, "none"); + assert.strictEqual(style.borderTopWidth, "medium"); + + style.border = null; style.borderTopWidth = "1px"; - style.borderLeftWidth = "1px"; - assert.strictEqual(style.cssText, "border-top-width: 1px; border-left-width: 1px;"); - style.borderTop = "none"; - assert.strictEqual(style.cssText, "border-left-width: 1px;"); + assert.strictEqual(style.cssText, "border-top-width: 1px;"); + style.borderTopStyle = "none"; + assert.strictEqual(style.cssText, "border-top-width: 1px; border-top-style: none;"); + assert.strictEqual(style.borderTopStyle, "none"); + assert.strictEqual(style.borderTopWidth, "1px"); }); it("setting border to 0 should be okay", () => { diff --git a/test/parsers.test.js b/test/parsers.test.js index 8077ab02..94ffc847 100644 --- a/test/parsers.test.js +++ b/test/parsers.test.js @@ -1545,9 +1545,39 @@ describe("parsePropertyValue", () => { it("should get string", () => { const property = "color"; - const value = "green"; + const value = "Green"; const output = parsers.parsePropertyValue(property, value); - assert.strictEqual(output, "green"); + assert.strictEqual(output, "Green"); + }); + + it("should get array", () => { + const property = "color"; + const value = "Green"; + const output = parsers.parsePropertyValue(property, value, { + inArray: true + }); + assert.deepEqual(output, [ + { + type: "Identifier", + name: "green" + } + ]); + }); + + it("should get string", () => { + const property = "color"; + const value = "Green"; + const output = parsers.parsePropertyValue(property, value, { + inArray: true, + caseSensitive: true + }); + assert.deepEqual(output, [ + { + type: "Identifier", + name: "Green", + loc: null + } + ]); }); it("should get string", () => { @@ -1575,53 +1605,52 @@ describe("parsePropertyValue", () => { it("should get array", () => { const property = "color"; - const value = "green"; + const value = "rgb(0 128 0)"; const output = parsers.parsePropertyValue(property, value, { inArray: true }); assert.deepEqual(output, [ { - type: "Identifier", - loc: null, - name: "green" + type: "Function", + name: "rgb", + value: "0 128 0", + raw: "rgb(0 128 0)" } ]); }); it("should get array", () => { - const property = "color"; - const value = "rgb(0 128 0)"; + const property = "background-image"; + const value = "none"; const output = parsers.parsePropertyValue(property, value, { inArray: true }); assert.deepEqual(output, [ { - type: "Function", - name: "rgb", - value: "0 128 0", - raw: "rgb(0 128 0)" + type: "Identifier", + name: "none" } ]); }); it("should get array", () => { const property = "background-image"; - const value = "none"; + const value = "url(example.png)"; const output = parsers.parsePropertyValue(property, value, { inArray: true }); assert.deepEqual(output, [ { - type: "Identifier", + type: "Url", loc: null, - name: "none" + value: "example.png" } ]); }); it("should get array", () => { const property = "background-image"; - const value = "url(example.png)"; + const value = "url(Example.png)"; const output = parsers.parsePropertyValue(property, value, { inArray: true }); @@ -1629,7 +1658,7 @@ describe("parsePropertyValue", () => { { type: "Url", loc: null, - value: "example.png" + value: "Example.png" } ]); }); diff --git a/test/properties.test.js b/test/properties.test.js index 3d2f58d9..4594780b 100644 --- a/test/properties.test.js +++ b/test/properties.test.js @@ -717,8 +717,7 @@ describe("border", () => { ); }); - // FIXME: - it.skip("border-style should set / get keyword", () => { + it("border-style should set / get keyword", () => { testImplicitPropertyValue( "border-style", "none", @@ -1784,15 +1783,6 @@ describe("font", () => { ); }); - // FIXME: - it.skip("font-variant should set / get keyword", () => { - testPropertyValue( - "font-variant", - "annotation(flowing) ornaments(flowing) swash(flowing) character-variant(flowing) styleset(flowing) historical-forms stylistic(flowing)", - "stylistic(flowing) historical-forms styleset(flowing) character-variant(flowing) swash(flowing) ornaments(flowing) annotation(flowing)" - ); - }); - it("font-variant should set / get keyword", () => { testPropertyValue("font-variant", "jis78", "jis78"); }); @@ -1809,15 +1799,15 @@ describe("font", () => { testPropertyValue("font-variant", "super", "super"); }); - it.skip("font-variant should not set / get invalid keywords", () => { + it("font-variant should not set / get invalid keywords", () => { testPropertyValue("font-variant", "normal none", ""); }); - it.skip("font-variant should not set / get invalid keywords", () => { + it("font-variant should not set / get invalid keywords", () => { testPropertyValue("font-variant", "normal small-caps", ""); }); - it.skip("font-variant should not set / get invalid keywords", () => { + it("font-variant should not set / get invalid keywords", () => { testPropertyValue("font-variant", "none small-caps", ""); }); From 29d3a59b1be1ce7edde7644c60f33b3dc02026ad Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Mon, 11 Aug 2025 21:22:30 +0900 Subject: [PATCH 36/41] Fix borders --- lib/CSSStyleDeclaration.js | 39 ++---- lib/parsers.js | 11 ++ lib/properties/border.js | 182 +++++++++++++++++++++++++--- lib/properties/borderBottom.js | 146 +++++++++++++++++++++- lib/properties/borderBottomStyle.js | 36 ++++-- lib/properties/borderBottomWidth.js | 42 +++++-- lib/properties/borderColor.js | 48 ++++++-- lib/properties/borderLeft.js | 146 +++++++++++++++++++++- lib/properties/borderLeftStyle.js | 36 ++++-- lib/properties/borderLeftWidth.js | 42 +++++-- lib/properties/borderRight.js | 146 +++++++++++++++++++++- lib/properties/borderRightStyle.js | 36 ++++-- lib/properties/borderRightWidth.js | 42 +++++-- lib/properties/borderStyle.js | 52 ++++++-- lib/properties/borderTop.js | 146 +++++++++++++++++++++- lib/properties/borderTopStyle.js | 36 ++++-- lib/properties/borderTopWidth.js | 42 +++++-- lib/properties/borderWidth.js | 65 ++++++++-- lib/properties/flex.js | 8 +- lib/properties/margin.js | 18 +-- lib/properties/padding.js | 18 +-- lib/shorthandProperties.js | 12 +- test/CSSStyleDeclaration.test.js | 23 ++-- 23 files changed, 1150 insertions(+), 222 deletions(-) diff --git a/lib/CSSStyleDeclaration.js b/lib/CSSStyleDeclaration.js index 70cff953..396eef42 100644 --- a/lib/CSSStyleDeclaration.js +++ b/lib/CSSStyleDeclaration.js @@ -124,6 +124,7 @@ class CSSStyleDeclaration { } } + // FIXME: get cssText() { if (this._computed) { return ""; @@ -136,15 +137,17 @@ class CSSStyleDeclaration { if (priority === "important") { properties.set(property, `${property}: ${value} !${priority};`); } else { - if (shorthandProperties.has(property)) { - const longhandProperties = shorthandProperties.get(property); - for (const [longhand] of longhandProperties) { - if (properties.has(longhand) && !this.getPropertyPriority(longhand)) { - properties.delete(longhand); - } + properties.set(property, `${property}: ${value};`); + } + } + for (const [property] of properties) { + if (shorthandProperties.has(property)) { + const longhandProperties = shorthandProperties.get(property); + for (const [longhand] of longhandProperties) { + if (properties.has(longhand) && !this.getPropertyPriority(longhand)) { + properties.delete(longhand); } } - properties.set(property, `${property}: ${value};`); } } return [...properties.values()].join(" "); @@ -491,28 +494,6 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { enumerable: false }, - // Companion to shorthandSetter, but for the individual parts which takes - // position value in the middle. - _midShorthandSetter: { - /** - * @param {string} property - * @param {string} val - * @param {object} shorthandFor - * @param {Array.} positions - */ - value(property, val, shorthandFor, positions = []) { - const obj = this._shorthandSetter(property, val, shorthandFor); - if (!obj) { - return; - } - for (const position of positions) { - this.removeProperty(`${property}-${position}`); - this._values.set(`${property}-${position}`, val); - } - }, - enumerable: false - }, - _implicitSetter: { /** * @param {string} prefix diff --git a/lib/parsers.js b/lib/parsers.js index 6e97e274..6e8de875 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -607,6 +607,9 @@ exports.parseColor = function parseColor(val) { break; } case "Identifier": { + if (SYS_COLOR.includes(name)) { + return name; + } const res = resolveColor(name, { format: "specifiedValue" }); @@ -740,6 +743,14 @@ exports.parsePropertyValue = function parsePropertyValue(prop, val, opt = {}) { return lowerCasedValue; } else if (SYS_COLOR.includes(lowerCasedValue)) { if (/^(?:-webkit-)?(?:[a-z][a-z\d]*-)*color$/i.test(prop)) { + if (inArray) { + return [ + { + type: "Identifier", + name: lowerCasedValue + } + ]; + } return lowerCasedValue; } return; diff --git a/lib/properties/border.js b/lib/properties/border.js index 1b25ff73..2e34f245 100644 --- a/lib/properties/border.js +++ b/lib/properties/border.js @@ -4,6 +4,23 @@ const parsers = require("../parsers"); const borderWidth = require("./borderWidth"); const borderStyle = require("./borderStyle"); const borderColor = require("./borderColor"); +const borderTop = require("./borderTop"); +const borderRight = require("./borderRight"); +const borderBottom = require("./borderBottom"); +const borderLeft = require("./borderLeft"); + +const initialValues = new Map([ + ["border-width", "medium"], + ["border-style", "none"], + ["border-color", "currentcolor"] +]); + +const positionShorthandFor = new Map([ + ["border-top", borderTop], + ["border-right", borderRight], + ["border-bottom", borderBottom], + ["border-left", borderLeft] +]); module.exports.shorthandFor = new Map([ ["border-width", borderWidth], @@ -11,36 +28,165 @@ module.exports.shorthandFor = new Map([ ["border-color", borderColor] ]); +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; + if (v === "") { + return v; + } + const values = parsers.splitValue(v); + const parsedValues = new Map(); + for (const val of values) { + const value = parsers.parsePropertyValue("border", val, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, type, value: itemValue }] = value; + switch (type) { + case "Calc": { + if (isNumber || parsedValues.has("border-width")) { + return; + } + parsedValues.set("border-width", `${name}(${itemValue})`); + break; + } + case "Dimension": + case "Number": { + if (parsedValues.has("border-width")) { + return; + } + const parsedValue = parsers.parseLength(value, { + min: 0 + }); + if (!parsedValue) { + return; + } + parsedValues.set("border-width", parsedValue); + break; + } + case "Function": { + if (parsedValues.has("border-color")) { + return; + } + const parsedValue = parsers.parseColor(value); + if (!parsedValue) { + return; + } + parsedValues.set("border-color", parsedValue); + break; + } + case "GlobalKeyword": { + if (values.length !== 1) { + return; + } + for (const key of module.exports.shorthandFor.keys()) { + parsedValues.set(key, name); + } + break; + } + case "Hash": { + if (parsedValues.has("border-color")) { + return; + } + const parsedValue = parsers.parseColor(`#${itemValue}`); + if (!parsedValue) { + return; + } + parsedValues.set("border-color", parsedValue); + break; + } + case "Identifier": { + if (parsers.isValidPropertyValue("border-width", name)) { + if (parsedValues.has("border-width")) { + return; + } + parsedValues.set("border-width", name); + break; + } else if (parsers.isValidPropertyValue("border-style", name)) { + if (parsedValues.has("border-style")) { + return; + } + parsedValues.set("border-style", name); + break; + } else if (parsers.isValidPropertyValue("border-color", name)) { + if (parsedValues.has("border-color")) { + return; + } + parsedValues.set("border-color", name); + break; + } + return; + } + default: { + return; + } + } + } else { + return; + } + } + if (parsedValues.size) { + const keys = module.exports.shorthandFor.keys(); + const obj = {}; + for (const key of keys) { + if (parsedValues.has(key)) { + obj[key] = parsedValues.get(key); + } + } + return obj; + } +}; + module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); - if (/^none$/i.test(v)) { - v = ""; - } if (parsers.hasVarFunc(v)) { for (const [key] of module.exports.shorthandFor) { this._setProperty(key, ""); } + for (const [key, parser] of positionShorthandFor) { + this._setProperty(key, ""); + for (const [subkey] of parser.shorthandFor) { + this._setProperty(subkey, ""); + } + } this._setProperty("border", v); + this._setProperty("border-image", "none"); } else { - this._midShorthandSetter("border", v, module.exports.shorthandFor, [ - "top", - "right", - "bottom", - "left" - ]); + const obj = module.exports.parse(v, { + globalObject: this._global + }); + if (obj === "") { + for (const [key] of module.exports.shorthandFor) { + this._setProperty(key, ""); + } + for (const [key, parser] of positionShorthandFor) { + this._setProperty(key, ""); + for (const [subkey] of parser.shorthandFor) { + this._setProperty(subkey, ""); + } + } + this._setProperty("border", ""); + this._setProperty("border-image", ""); + } else if (obj) { + const valueObj = Object.fromEntries(initialValues); + for (const key of Object.keys(obj)) { + valueObj[key] = obj[key]; + } + const positions = ["top", "right", "bottom", "left"]; + for (const [key, value] of Object.entries(valueObj)) { + this._setProperty(key, value); + const [prefix, suffix] = key.split("-"); + const { parse: parser } = module.exports.shorthandFor.get(key); + this._implicitSetter(prefix, suffix, value, parser, positions); + } + this._setProperty("border", [...Object.values(obj)].join(" ")); + this._setProperty("border-image", "none"); + } } }, get() { - let val = this.getPropertyValue("border"); - if (parsers.hasVarFunc(val)) { - return val; - } - val = this._shorthandGetter("border", module.exports.shorthandFor); - if (parsers.hasVarFunc(val)) { - return ""; - } - return val; + return this.getPropertyValue("border"); }, enumerable: true, configurable: true diff --git a/lib/properties/borderBottom.js b/lib/properties/borderBottom.js index 24426734..1bf00361 100644 --- a/lib/properties/borderBottom.js +++ b/lib/properties/borderBottom.js @@ -5,12 +5,127 @@ const borderBottomWidth = require("./borderBottomWidth"); const borderBottomStyle = require("./borderBottomStyle"); const borderBottomColor = require("./borderBottomColor"); +const initialValues = new Map([ + ["border-bottom-width", "medium"], + ["border-bottom-style", "none"], + ["border-bottom-color", "currentcolor"] +]); + module.exports.shorthandFor = new Map([ ["border-bottom-width", borderBottomWidth], ["border-bottom-style", borderBottomStyle], ["border-bottom-color", borderBottomColor] ]); +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; + if (v === "") { + return v; + } + const values = parsers.splitValue(v); + const parsedValues = new Map(); + for (const val of values) { + const value = parsers.parsePropertyValue("border-bottom", val, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, type, value: itemValue }] = value; + switch (type) { + case "Calc": { + if (isNumber || parsedValues.has("border-bottom-width")) { + return; + } + parsedValues.set("border-bottom-width", `${name}(${itemValue})`); + break; + } + case "Dimension": + case "Number": { + if (parsedValues.has("border-bottom-width")) { + return; + } + const parsedValue = parsers.parseLength(value, { + min: 0 + }); + if (!parsedValue) { + return; + } + parsedValues.set("border-bottom-width", parsedValue); + break; + } + case "Function": { + if (parsedValues.has("border-bottom-color")) { + return; + } + const parsedValue = parsers.parseColor(value); + if (!parsedValue) { + return; + } + parsedValues.set("border-bottom-color", parsedValue); + break; + } + case "GlobalKeyword": { + if (values.length !== 1) { + return; + } + for (const key of module.exports.shorthandFor.keys()) { + parsedValues.set(key, name); + } + break; + } + case "Hash": { + if (parsedValues.has("border-bottom-color")) { + return; + } + const parsedValue = parsers.parseColor(`#${itemValue}`); + if (!parsedValue) { + return; + } + parsedValues.set("border-bottom-color", parsedValue); + break; + } + case "Identifier": { + if (parsers.isValidPropertyValue("border-bottom-width", name)) { + if (parsedValues.has("border-bottom-width")) { + return; + } + parsedValues.set("border-bottom-width", name); + break; + } else if (parsers.isValidPropertyValue("border-bottom-style", name)) { + if (parsedValues.has("border-bottom-style")) { + return; + } + parsedValues.set("border-bottom-style", name); + break; + } else if (parsers.isValidPropertyValue("border-bottom-color", name)) { + if (parsedValues.has("border-bottom-color")) { + return; + } + parsedValues.set("border-bottom-color", name); + break; + } + return; + } + default: { + return; + } + } + } else { + return; + } + } + if (parsedValues.size) { + const keys = module.exports.shorthandFor.keys(); + const obj = {}; + for (const key of keys) { + if (parsedValues.has(key)) { + obj[key] = parsedValues.get(key); + } + } + return obj; + } +}; + module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); @@ -21,19 +136,38 @@ module.exports.definition = { this._setProperty("border", ""); this._setProperty("border-bottom", v); } else { - this._shorthandSetter("border-bottom", v, module.exports.shorthandFor); + const obj = module.exports.parse(v, { + globalObject: this._global + }); + if (obj === "") { + for (const [key] of module.exports.shorthandFor) { + this._setProperty(key, ""); + } + this._setProperty("border", ""); + this._setProperty("border-bottom", ""); + } else if (obj) { + const valueObj = Object.fromEntries(initialValues); + for (const key of Object.keys(obj)) { + valueObj[key] = obj[key]; + } + for (const [key, value] of Object.entries(valueObj)) { + this._setProperty(key, value); + } + this._setProperty("border", ""); + this._setProperty("border-bottom", [...Object.values(obj)].join(" ")); + } } }, get() { - let val = this.getPropertyValue("border-bottom"); - if (parsers.hasVarFunc(val)) { + const val = this.getPropertyValue("border-bottom"); + if (parsers.isGlobalKeyword(val) || parsers.hasVarFunc(val)) { return val; } - val = this._shorthandGetter("border-bottom", module.exports.shorthandFor); - if (parsers.hasVarFunc(val)) { + const subVal = this._shorthandGetter("border-bottom", module.exports.shorthandFor); + if (parsers.hasVarFunc(subVal)) { return ""; } - return val; + return subVal; }, enumerable: true, configurable: true diff --git a/lib/properties/borderBottomStyle.js b/lib/properties/borderBottomStyle.js index 029b9797..4722659a 100644 --- a/lib/properties/borderBottomStyle.js +++ b/lib/properties/borderBottomStyle.js @@ -1,13 +1,27 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.isValidPropertyValue("border-bottom-style", v)) { - return strings.asciiLowercase(v); + } + const value = parsers.parsePropertyValue("border-bottom-style", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: + } + } else if (typeof value === "string") { + return value; } }; @@ -20,14 +34,12 @@ module.exports.definition = { this._setProperty("border-style", ""); this._setProperty("border-bottom-style", v); } else { - const val = module.exports.parse(v); - if (val === "" || val === "none" || val === "hidden") { - this._setProperty("border-bottom-style", ""); - this._setProperty("border-bottom-color", ""); - this._setProperty("border-bottom-width", ""); - } else { - this._setProperty("border-bottom-style", val); - } + this._setProperty( + "border-bottom-style", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/borderBottomWidth.js b/lib/properties/borderBottomWidth.js index 741ab211..189dec83 100644 --- a/lib/properties/borderBottomWidth.js +++ b/lib/properties/borderBottomWidth.js @@ -1,20 +1,37 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("border-bottom-width", v)) { - const val = parsers.parseLength(v, true); - if (val) { - return val; + const value = parsers.parsePropertyValue("border-bottom-width", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, type, value: itemValue }] = value; + switch (type) { + case "Calc": { + if (isNumber) { + return; + } + return `${name}(${itemValue})`; + } + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: { + return parsers.parseLength(value, { + min: 0 + }); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; @@ -27,7 +44,12 @@ module.exports.definition = { this._setProperty("border-bottom", ""); this._setProperty("border-bottom-width", v); } else { - this._setProperty("border-bottom-width", module.exports.parse(v)); + this._setProperty( + "border-bottom-width", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/borderColor.js b/lib/properties/borderColor.js index 0eea31a8..8ac11cc8 100644 --- a/lib/properties/borderColor.js +++ b/lib/properties/borderColor.js @@ -7,22 +7,39 @@ module.exports.parse = function parse(v, opt = {}) { if (v === "") { return v; } - const value = parsers.parsePropertyValue("border-color", v, { + const values = parsers.parsePropertyValue("border-color", v, { globalObject, inArray: true }); - if (Array.isArray(value) && value.length === 1) { - const [{ name, type }] = value; - switch (type) { - case "GlobalKeyword": { - return name; - } - default: { - return parsers.parseColor(value); + const parsedValues = []; + if (Array.isArray(values) && values.length) { + if (values.length > 4) { + return; + } + for (const value of values) { + const { name, type } = value; + switch (type) { + case "GlobalKeyword": { + if (values.length !== 1) { + return; + } + parsedValues.push(name); + break; + } + default: { + const parsedValue = parsers.parseColor([value]); + if (!parsedValue) { + return; + } + parsedValues.push(parsedValue); + } } } - } else if (typeof value === "string") { - return value; + } else if (typeof values === "string") { + parsedValues.push(values); + } + if (parsedValues.length) { + return parsedValues.join(" "); } }; @@ -33,8 +50,13 @@ module.exports.definition = { this._setProperty("border", ""); this._setProperty("border-color", v); } else { - const positions = ["top", "right", "bottom", "left"]; - this._implicitSetter("border", "color", v, module.exports.parse, positions); + this._setProperty("border", ""); + this._implicitSetter("border", "color", v, module.exports.parse, [ + "top", + "right", + "bottom", + "left" + ]); } }, get() { diff --git a/lib/properties/borderLeft.js b/lib/properties/borderLeft.js index e283dcd6..cf15a627 100644 --- a/lib/properties/borderLeft.js +++ b/lib/properties/borderLeft.js @@ -5,12 +5,127 @@ const borderLeftWidth = require("./borderLeftWidth"); const borderLeftStyle = require("./borderLeftStyle"); const borderLeftColor = require("./borderLeftColor"); +const initialValues = new Map([ + ["border-left-width", "medium"], + ["border-left-style", "none"], + ["border-left-color", "currentcolor"] +]); + module.exports.shorthandFor = new Map([ ["border-left-width", borderLeftWidth], ["border-left-style", borderLeftStyle], ["border-left-color", borderLeftColor] ]); +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; + if (v === "") { + return v; + } + const values = parsers.splitValue(v); + const parsedValues = new Map(); + for (const val of values) { + const value = parsers.parsePropertyValue("border-left", val, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, type, value: itemValue }] = value; + switch (type) { + case "Calc": { + if (isNumber || parsedValues.has("border-left-width")) { + return; + } + parsedValues.set("border-left-width", `${name}(${itemValue})`); + break; + } + case "Dimension": + case "Number": { + if (parsedValues.has("border-left-width")) { + return; + } + const parsedValue = parsers.parseLength(value, { + min: 0 + }); + if (!parsedValue) { + return; + } + parsedValues.set("border-left-width", parsedValue); + break; + } + case "Function": { + if (parsedValues.has("border-left-color")) { + return; + } + const parsedValue = parsers.parseColor(value); + if (!parsedValue) { + return; + } + parsedValues.set("border-left-color", parsedValue); + break; + } + case "GlobalKeyword": { + if (values.length !== 1) { + return; + } + for (const key of module.exports.shorthandFor.keys()) { + parsedValues.set(key, name); + } + break; + } + case "Hash": { + if (parsedValues.has("border-left-color")) { + return; + } + const parsedValue = parsers.parseColor(`#${itemValue}`); + if (!parsedValue) { + return; + } + parsedValues.set("border-left-color", parsedValue); + break; + } + case "Identifier": { + if (parsers.isValidPropertyValue("border-left-width", name)) { + if (parsedValues.has("border-left-width")) { + return; + } + parsedValues.set("border-left-width", name); + break; + } else if (parsers.isValidPropertyValue("border-left-style", name)) { + if (parsedValues.has("border-left-style")) { + return; + } + parsedValues.set("border-left-style", name); + break; + } else if (parsers.isValidPropertyValue("border-left-color", name)) { + if (parsedValues.has("border-left-color")) { + return; + } + parsedValues.set("border-left-color", name); + break; + } + return; + } + default: { + return; + } + } + } else { + return; + } + } + if (parsedValues.size) { + const keys = module.exports.shorthandFor.keys(); + const obj = {}; + for (const key of keys) { + if (parsedValues.has(key)) { + obj[key] = parsedValues.get(key); + } + } + return obj; + } +}; + module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); @@ -21,19 +136,38 @@ module.exports.definition = { this._setProperty("border", ""); this._setProperty("border-left", v); } else { - this._shorthandSetter("border-left", v, module.exports.shorthandFor); + const obj = module.exports.parse(v, { + globalObject: this._global + }); + if (obj === "") { + for (const [key] of module.exports.shorthandFor) { + this._setProperty(key, ""); + } + this._setProperty("border", ""); + this._setProperty("border-left", ""); + } else if (obj) { + const valueObj = Object.fromEntries(initialValues); + for (const key of Object.keys(obj)) { + valueObj[key] = obj[key]; + } + for (const [key, value] of Object.entries(valueObj)) { + this._setProperty(key, value); + } + this._setProperty("border", ""); + this._setProperty("border-left", [...Object.values(obj)].join(" ")); + } } }, get() { - let val = this.getPropertyValue("border-left"); - if (parsers.hasVarFunc(val)) { + const val = this.getPropertyValue("border-left"); + if (parsers.isGlobalKeyword(val) || parsers.hasVarFunc(val)) { return val; } - val = this._shorthandGetter("border-left", module.exports.shorthandFor); - if (parsers.hasVarFunc(val)) { + const subVal = this._shorthandGetter("border-left", module.exports.shorthandFor); + if (parsers.hasVarFunc(subVal)) { return ""; } - return val; + return subVal; }, enumerable: true, configurable: true diff --git a/lib/properties/borderLeftStyle.js b/lib/properties/borderLeftStyle.js index 8aa6bdb6..e9dc510b 100644 --- a/lib/properties/borderLeftStyle.js +++ b/lib/properties/borderLeftStyle.js @@ -1,13 +1,27 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.isValidPropertyValue("border-left-style", v)) { - return strings.asciiLowercase(v); + } + const value = parsers.parsePropertyValue("border-left-style", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: + } + } else if (typeof value === "string") { + return value; } }; @@ -20,14 +34,12 @@ module.exports.definition = { this._setProperty("border-style", ""); this._setProperty("border-left-style", v); } else { - const val = module.exports.parse(v); - if (val === "" || val === "none" || val === "hidden") { - this._setProperty("border-left-style", ""); - this._setProperty("border-left-color", ""); - this._setProperty("border-left-width", ""); - } else { - this._setProperty("border-left-style", val); - } + this._setProperty( + "border-left-style", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/borderLeftWidth.js b/lib/properties/borderLeftWidth.js index 189bf62f..ba6d0c88 100644 --- a/lib/properties/borderLeftWidth.js +++ b/lib/properties/borderLeftWidth.js @@ -1,20 +1,37 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("border-left-width", v)) { - const val = parsers.parseLength(v, true); - if (val) { - return val; + const value = parsers.parsePropertyValue("border-left-width", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, type, value: itemValue }] = value; + switch (type) { + case "Calc": { + if (isNumber) { + return; + } + return `${name}(${itemValue})`; + } + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: { + return parsers.parseLength(value, { + min: 0 + }); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; @@ -27,7 +44,12 @@ module.exports.definition = { this._setProperty("border-left", ""); this._setProperty("border-left-width", v); } else { - this._setProperty("border-left-width", module.exports.parse(v)); + this._setProperty( + "border-left-width", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/borderRight.js b/lib/properties/borderRight.js index 4f911910..49132a6a 100644 --- a/lib/properties/borderRight.js +++ b/lib/properties/borderRight.js @@ -5,12 +5,127 @@ const borderRightWidth = require("./borderRightWidth"); const borderRightStyle = require("./borderRightStyle"); const borderRightColor = require("./borderRightColor"); +const initialValues = new Map([ + ["border-right-width", "medium"], + ["border-right-style", "none"], + ["border-right-color", "currentcolor"] +]); + module.exports.shorthandFor = new Map([ ["border-right-width", borderRightWidth], ["border-right-style", borderRightStyle], ["border-right-color", borderRightColor] ]); +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; + if (v === "") { + return v; + } + const values = parsers.splitValue(v); + const parsedValues = new Map(); + for (const val of values) { + const value = parsers.parsePropertyValue("border-right", val, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, type, value: itemValue }] = value; + switch (type) { + case "Calc": { + if (isNumber || parsedValues.has("border-right-width")) { + return; + } + parsedValues.set("border-right-width", `${name}(${itemValue})`); + break; + } + case "Dimension": + case "Number": { + if (parsedValues.has("border-right-width")) { + return; + } + const parsedValue = parsers.parseLength(value, { + min: 0 + }); + if (!parsedValue) { + return; + } + parsedValues.set("border-right-width", parsedValue); + break; + } + case "Function": { + if (parsedValues.has("border-right-color")) { + return; + } + const parsedValue = parsers.parseColor(value); + if (!parsedValue) { + return; + } + parsedValues.set("border-right-color", parsedValue); + break; + } + case "GlobalKeyword": { + if (values.length !== 1) { + return; + } + for (const key of module.exports.shorthandFor.keys()) { + parsedValues.set(key, name); + } + break; + } + case "Hash": { + if (parsedValues.has("border-right-color")) { + return; + } + const parsedValue = parsers.parseColor(`#${itemValue}`); + if (!parsedValue) { + return; + } + parsedValues.set("border-right-color", parsedValue); + break; + } + case "Identifier": { + if (parsers.isValidPropertyValue("border-right-width", name)) { + if (parsedValues.has("border-right-width")) { + return; + } + parsedValues.set("border-right-width", name); + break; + } else if (parsers.isValidPropertyValue("border-right-style", name)) { + if (parsedValues.has("border-right-style")) { + return; + } + parsedValues.set("border-right-style", name); + break; + } else if (parsers.isValidPropertyValue("border-right-color", name)) { + if (parsedValues.has("border-right-color")) { + return; + } + parsedValues.set("border-right-color", name); + break; + } + return; + } + default: { + return; + } + } + } else { + return; + } + } + if (parsedValues.size) { + const keys = module.exports.shorthandFor.keys(); + const obj = {}; + for (const key of keys) { + if (parsedValues.has(key)) { + obj[key] = parsedValues.get(key); + } + } + return obj; + } +}; + module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); @@ -21,19 +136,38 @@ module.exports.definition = { this._setProperty("border", ""); this._setProperty("border-right", v); } else { - this._shorthandSetter("border-right", v, module.exports.shorthandFor); + const obj = module.exports.parse(v, { + globalObject: this._global + }); + if (obj === "") { + for (const [key] of module.exports.shorthandFor) { + this._setProperty(key, ""); + } + this._setProperty("border", ""); + this._setProperty("border-right", ""); + } else if (obj) { + const valueObj = Object.fromEntries(initialValues); + for (const key of Object.keys(obj)) { + valueObj[key] = obj[key]; + } + for (const [key, value] of Object.entries(valueObj)) { + this._setProperty(key, value); + } + this._setProperty("border", ""); + this._setProperty("border-right", [...Object.values(obj)].join(" ")); + } } }, get() { - let val = this.getPropertyValue("border-right"); - if (parsers.hasVarFunc(val)) { + const val = this.getPropertyValue("border-right"); + if (parsers.isGlobalKeyword(val) || parsers.hasVarFunc(val)) { return val; } - val = this._shorthandGetter("border-right", module.exports.shorthandFor); - if (parsers.hasVarFunc(val)) { + const subVal = this._shorthandGetter("border-right", module.exports.shorthandFor); + if (parsers.hasVarFunc(subVal)) { return ""; } - return val; + return subVal; }, enumerable: true, configurable: true diff --git a/lib/properties/borderRightStyle.js b/lib/properties/borderRightStyle.js index c8bed47a..8220ea11 100644 --- a/lib/properties/borderRightStyle.js +++ b/lib/properties/borderRightStyle.js @@ -1,13 +1,27 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.isValidPropertyValue("border-right-style", v)) { - return strings.asciiLowercase(v); + } + const value = parsers.parsePropertyValue("border-right-style", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: + } + } else if (typeof value === "string") { + return value; } }; @@ -20,14 +34,12 @@ module.exports.definition = { this._setProperty("border-style", ""); this._setProperty("border-right-style", v); } else { - const val = module.exports.parse(v); - if (val === "" || val === "none" || val === "hidden") { - this._setProperty("border-right-style", ""); - this._setProperty("border-right-color", ""); - this._setProperty("border-right-width", ""); - } else { - this._setProperty("border-right-style", val); - } + this._setProperty( + "border-right-style", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/borderRightWidth.js b/lib/properties/borderRightWidth.js index b035376c..ba7b798d 100644 --- a/lib/properties/borderRightWidth.js +++ b/lib/properties/borderRightWidth.js @@ -1,20 +1,37 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("border-right-width", v)) { - const val = parsers.parseLength(v, true); - if (val) { - return val; + const value = parsers.parsePropertyValue("border-right-width", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, type, value: itemValue }] = value; + switch (type) { + case "Calc": { + if (isNumber) { + return; + } + return `${name}(${itemValue})`; + } + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: { + return parsers.parseLength(value, { + min: 0 + }); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; @@ -27,7 +44,12 @@ module.exports.definition = { this._setProperty("border-right", ""); this._setProperty("border-right-width", v); } else { - this._setProperty("border-right-width", module.exports.parse(v)); + this._setProperty( + "border-right-width", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/borderStyle.js b/lib/properties/borderStyle.js index 2c7cd008..09980b4f 100644 --- a/lib/properties/borderStyle.js +++ b/lib/properties/borderStyle.js @@ -1,13 +1,45 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.isValidPropertyValue("border-style", v)) { - return strings.asciiLowercase(v); + } + const values = parsers.parsePropertyValue("border-style", v, { + globalObject, + inArray: true + }); + const parsedValues = []; + if (Array.isArray(values) && values.length) { + if (values.length > 4) { + return; + } + for (const value of values) { + const { name, type } = value; + switch (type) { + case "GlobalKeyword": { + if (values.length !== 1) { + return; + } + parsedValues.push(name); + break; + } + case "Identifier": { + parsedValues.push(name); + break; + } + default: { + return; + } + } + } + } else if (typeof values === "string") { + parsedValues.push(values); + } + if (parsedValues.length) { + return parsedValues.join(" "); } }; @@ -18,11 +50,13 @@ module.exports.definition = { this._setProperty("border", ""); this._setProperty("border-style", v); } else { - const positions = ["top", "right", "bottom", "left"]; - if (/^none$/i.test(v)) { - v = ""; - } - this._implicitSetter("border", "style", v, module.exports.parse, positions); + this._setProperty("border", ""); + this._implicitSetter("border", "style", v, module.exports.parse, [ + "top", + "right", + "bottom", + "left" + ]); } }, get() { diff --git a/lib/properties/borderTop.js b/lib/properties/borderTop.js index 1b25197d..091ae1b3 100644 --- a/lib/properties/borderTop.js +++ b/lib/properties/borderTop.js @@ -5,12 +5,127 @@ const borderTopWidth = require("./borderTopWidth"); const borderTopStyle = require("./borderTopStyle"); const borderTopColor = require("./borderTopColor"); +const initialValues = new Map([ + ["border-top-width", "medium"], + ["border-top-style", "none"], + ["border-top-color", "currentcolor"] +]); + module.exports.shorthandFor = new Map([ ["border-top-width", borderTopWidth], ["border-top-style", borderTopStyle], ["border-top-color", borderTopColor] ]); +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; + if (v === "") { + return v; + } + const values = parsers.splitValue(v); + const parsedValues = new Map(); + for (const val of values) { + const value = parsers.parsePropertyValue("border-top", val, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, type, value: itemValue }] = value; + switch (type) { + case "Calc": { + if (isNumber || parsedValues.has("border-top-width")) { + return; + } + parsedValues.set("border-top-width", `${name}(${itemValue})`); + break; + } + case "Dimension": + case "Number": { + if (parsedValues.has("border-top-width")) { + return; + } + const parsedValue = parsers.parseLength(value, { + min: 0 + }); + if (!parsedValue) { + return; + } + parsedValues.set("border-top-width", parsedValue); + break; + } + case "Function": { + if (parsedValues.has("border-top-color")) { + return; + } + const parsedValue = parsers.parseColor(value); + if (!parsedValue) { + return; + } + parsedValues.set("border-top-color", parsedValue); + break; + } + case "GlobalKeyword": { + if (values.length !== 1) { + return; + } + for (const key of module.exports.shorthandFor.keys()) { + parsedValues.set(key, name); + } + break; + } + case "Hash": { + if (parsedValues.has("border-top-color")) { + return; + } + const parsedValue = parsers.parseColor(`#${itemValue}`); + if (!parsedValue) { + return; + } + parsedValues.set("border-top-color", parsedValue); + break; + } + case "Identifier": { + if (parsers.isValidPropertyValue("border-top-width", name)) { + if (parsedValues.has("border-top-width")) { + return; + } + parsedValues.set("border-top-width", name); + break; + } else if (parsers.isValidPropertyValue("border-top-style", name)) { + if (parsedValues.has("border-top-style")) { + return; + } + parsedValues.set("border-top-style", name); + break; + } else if (parsers.isValidPropertyValue("border-top-color", name)) { + if (parsedValues.has("border-top-color")) { + return; + } + parsedValues.set("border-top-color", name); + break; + } + return; + } + default: { + return; + } + } + } else { + return; + } + } + if (parsedValues.size) { + const keys = module.exports.shorthandFor.keys(); + const obj = {}; + for (const key of keys) { + if (parsedValues.has(key)) { + obj[key] = parsedValues.get(key); + } + } + return obj; + } +}; + module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); @@ -21,19 +136,38 @@ module.exports.definition = { this._setProperty("border", ""); this._setProperty("border-top", v); } else { - this._shorthandSetter("border-top", v, module.exports.shorthandFor); + const obj = module.exports.parse(v, { + globalObject: this._global + }); + if (obj === "") { + for (const [key] of module.exports.shorthandFor) { + this._setProperty(key, ""); + } + this._setProperty("border", ""); + this._setProperty("border-top", ""); + } else if (obj) { + const valueObj = Object.fromEntries(initialValues); + for (const key of Object.keys(obj)) { + valueObj[key] = obj[key]; + } + for (const [key, value] of Object.entries(valueObj)) { + this._setProperty(key, value); + } + this._setProperty("border", ""); + this._setProperty("border-top", [...Object.values(obj)].join(" ")); + } } }, get() { - let val = this.getPropertyValue("border-top"); - if (parsers.hasVarFunc(val)) { + const val = this.getPropertyValue("border-top"); + if (parsers.isGlobalKeyword(val) || parsers.hasVarFunc(val)) { return val; } - val = this._shorthandGetter("border-top", module.exports.shorthandFor); - if (parsers.hasVarFunc(val)) { + const subVal = this._shorthandGetter("border-top", module.exports.shorthandFor); + if (parsers.hasVarFunc(subVal)) { return ""; } - return val; + return subVal; }, enumerable: true, configurable: true diff --git a/lib/properties/borderTopStyle.js b/lib/properties/borderTopStyle.js index 4bbdeff7..f2dae236 100644 --- a/lib/properties/borderTopStyle.js +++ b/lib/properties/borderTopStyle.js @@ -1,13 +1,27 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.isValidPropertyValue("border-top-style", v)) { - return strings.asciiLowercase(v); + } + const value = parsers.parsePropertyValue("border-top-style", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ name, type }] = value; + switch (type) { + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: + } + } else if (typeof value === "string") { + return value; } }; @@ -20,14 +34,12 @@ module.exports.definition = { this._setProperty("border-style", ""); this._setProperty("border-top-style", v); } else { - const val = module.exports.parse(v); - if (val === "" || val === "none" || val === "hidden") { - this._setProperty("border-top-style", ""); - this._setProperty("border-top-color", ""); - this._setProperty("border-top-width", ""); - } else { - this._setProperty("border-top-style", val); - } + this._setProperty( + "border-top-style", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/borderTopWidth.js b/lib/properties/borderTopWidth.js index 709ae86d..fd9a20e2 100644 --- a/lib/properties/borderTopWidth.js +++ b/lib/properties/borderTopWidth.js @@ -1,20 +1,37 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("border-top-width", v)) { - const val = parsers.parseLength(v, true); - if (val) { - return val; + const value = parsers.parsePropertyValue("border-top-width", v, { + globalObject, + inArray: true + }); + if (Array.isArray(value) && value.length === 1) { + const [{ isNumber, name, type, value: itemValue }] = value; + switch (type) { + case "Calc": { + if (isNumber) { + return; + } + return `${name}(${itemValue})`; + } + case "GlobalKeyword": + case "Identifier": { + return name; + } + default: { + return parsers.parseLength(value, { + min: 0 + }); + } } - return strings.asciiLowercase(v); + } else if (typeof value === "string") { + return value; } }; @@ -27,7 +44,12 @@ module.exports.definition = { this._setProperty("border-top", ""); this._setProperty("border-top-width", v); } else { - this._setProperty("border-top-width", module.exports.parse(v)); + this._setProperty( + "border-top-width", + module.exports.parse(v, { + globalObject: this._global + }) + ); } }, get() { diff --git a/lib/properties/borderWidth.js b/lib/properties/borderWidth.js index 6a492055..06cc226c 100644 --- a/lib/properties/borderWidth.js +++ b/lib/properties/borderWidth.js @@ -1,20 +1,58 @@ "use strict"; const parsers = require("../parsers"); -const strings = require("../utils/strings"); -module.exports.parse = function parse(v) { +module.exports.parse = function parse(v, opt = {}) { + const { globalObject } = opt; if (v === "") { return v; - } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); } - if (parsers.isValidPropertyValue("border-width", v)) { - const val = parsers.parseLength(v, true); - if (val) { - return val; + const values = parsers.parsePropertyValue("border-width", v, { + globalObject, + inArray: true + }); + const parsedValues = []; + if (Array.isArray(values) && values.length) { + if (values.length > 4) { + return; } - return strings.asciiLowercase(v); + for (const value of values) { + const { isNumber, name, type, value: itemValue } = value; + switch (type) { + case "Calc": { + if (isNumber) { + return; + } + parsedValues.push(`${name}(${itemValue})`); + break; + } + case "GlobalKeyword": { + if (values.length !== 1) { + return; + } + parsedValues.push(name); + break; + } + case "Identifier": { + parsedValues.push(name); + break; + } + default: { + const parsedValue = parsers.parseLength([value], { + min: 0 + }); + if (!parsedValue) { + return; + } + parsedValues.push(parsedValue); + } + } + } + } else if (typeof values === "string") { + parsedValues.push(values); + } + if (parsedValues.length) { + return parsedValues.join(" "); } }; @@ -25,8 +63,13 @@ module.exports.definition = { this._setProperty("border", ""); this._setProperty("border-width", v); } else { - const positions = ["top", "right", "bottom", "left"]; - this._implicitSetter("border", "width", v, module.exports.parse, positions); + this._implicitSetter("border", "width", v, module.exports.parse, [ + "top", + "right", + "bottom", + "left" + ]); + this._setProperty("border", ""); } }, get() { diff --git a/lib/properties/flex.js b/lib/properties/flex.js index 6a93c26f..9d119344 100644 --- a/lib/properties/flex.js +++ b/lib/properties/flex.js @@ -132,15 +132,15 @@ module.exports.definition = { } }, get() { - let val = this.getPropertyValue("flex"); + const val = this.getPropertyValue("flex"); if (parsers.isGlobalKeyword(val) || parsers.hasVarFunc(val)) { return val; } - val = this._shorthandGetter("flex", module.exports.shorthandFor); - if (parsers.hasVarFunc(val)) { + const subVal = this._shorthandGetter("flex", module.exports.shorthandFor); + if (parsers.hasVarFunc(subVal)) { return ""; } - return val; + return subVal; }, enumerable: true, configurable: true diff --git a/lib/properties/margin.js b/lib/properties/margin.js index e66043a9..3f87dbce 100644 --- a/lib/properties/margin.js +++ b/lib/properties/margin.js @@ -9,17 +9,17 @@ module.exports.parse = function parse(v, opt = {}) { if (v === "") { return v; } - const value = parsers.parsePropertyValue("margin", v, { + const values = parsers.parsePropertyValue("margin", v, { globalObject, inArray: true }); const parsedValues = []; - if (Array.isArray(value) && value.length) { - if (value.length > 4) { + if (Array.isArray(values) && values.length) { + if (values.length > 4) { return; } - for (let i = 0; i < value.length; i++) { - const { isNumber, name, type, value: itemValue } = value[i]; + for (const value of values) { + const { isNumber, name, type, value: itemValue } = value; switch (type) { case "Calc": { if (isNumber) { @@ -29,7 +29,7 @@ module.exports.parse = function parse(v, opt = {}) { break; } case "GlobalKeyword": { - if (i !== 0) { + if (values.length !== 1) { return; } parsedValues.push(name); @@ -40,7 +40,7 @@ module.exports.parse = function parse(v, opt = {}) { break; } default: { - const parsedValue = parsers.parseMeasurement([value[i]]); + const parsedValue = parsers.parseMeasurement([value]); if (!parsedValue) { return; } @@ -48,8 +48,8 @@ module.exports.parse = function parse(v, opt = {}) { } } } - } else if (typeof value === "string") { - parsedValues.push(value); + } else if (typeof values === "string") { + parsedValues.push(values); } if (parsedValues.length) { return parsedValues.join(" "); diff --git a/lib/properties/padding.js b/lib/properties/padding.js index 401f7f23..ef2f65d1 100644 --- a/lib/properties/padding.js +++ b/lib/properties/padding.js @@ -9,17 +9,17 @@ module.exports.parse = function parse(v, opt = {}) { if (v === "") { return v; } - const value = parsers.parsePropertyValue("padding", v, { + const values = parsers.parsePropertyValue("padding", v, { globalObject, inArray: true }); const parsedValues = []; - if (Array.isArray(value) && value.length) { - if (value.length > 4) { + if (Array.isArray(values) && values.length) { + if (values.length > 4) { return; } - for (let i = 0; i < value.length; i++) { - const { isNumber, name, type, value: itemValue } = value[i]; + for (const value of values) { + const { isNumber, name, type, value: itemValue } = value; switch (type) { case "Calc": { if (isNumber) { @@ -29,14 +29,14 @@ module.exports.parse = function parse(v, opt = {}) { break; } case "GlobalKeyword": { - if (i !== 0) { + if (values.length !== 1) { return; } parsedValues.push(name); break; } default: { - const parsedValue = parsers.parseMeasurement([value[i]], { + const parsedValue = parsers.parseMeasurement([value], { min: 0 }); if (!parsedValue) { @@ -46,8 +46,8 @@ module.exports.parse = function parse(v, opt = {}) { } } } - } else if (typeof value === "string") { - parsedValues.push(value); + } else if (typeof values === "string") { + parsedValues.push(values); } if (parsedValues.length) { return parsedValues.join(" "); diff --git a/lib/shorthandProperties.js b/lib/shorthandProperties.js index 16d4daee..4b6b59da 100644 --- a/lib/shorthandProperties.js +++ b/lib/shorthandProperties.js @@ -11,7 +11,17 @@ const font = require("./properties/font"); module.exports.shorthandProperties = new Map([ ["background", background.shorthandFor], - ["border", border.shorthandFor], + [ + "border", + new Map([ + ...border.shorthandFor, + ...borderTop.shorthandFor, + ...borderRight.shorthandFor, + ...borderBottom.shorthandFor, + ...borderLeft.shorthandFor, + ["border-image", null] + ]) + ], ["border-top", borderTop.shorthandFor], ["border-right", borderRight.shorthandFor], ["border-bottom", borderBottom.shorthandFor], diff --git a/test/CSSStyleDeclaration.test.js b/test/CSSStyleDeclaration.test.js index 9f4639ca..00694410 100644 --- a/test/CSSStyleDeclaration.test.js +++ b/test/CSSStyleDeclaration.test.js @@ -354,7 +354,7 @@ describe("CSSStyleDeclaration", () => { assert.strictEqual(style.color, ""); }); - it("short hand properties with embedded spaces", () => { + it("shorthand properties with embedded spaces", () => { let style = new CSSStyleDeclaration(); style.background = "rgb(0, 0, 0) url(/something/somewhere.jpg)"; assert.strictEqual(style.backgroundColor, "rgb(0, 0, 0)"); @@ -393,26 +393,26 @@ describe("CSSStyleDeclaration", () => { assert.strictEqual(style.cssText, ""); }); - // FIXME: Not sure what the expected results should be. - it('setting border values to "none" should not clear dependent values', () => { + it('setting border values to "none" should change dependent values', () => { const style = new CSSStyleDeclaration(); style.borderTopWidth = "1px"; assert.strictEqual(style.cssText, "border-top-width: 1px;"); style.border = "none"; - // Firefox: "border: medium;", Chrome: "border: none;". - assert.strictEqual(style.cssText, "border-top-width: 1px; border: none;"); + assert.strictEqual(style.cssText, "border: none;"); assert.strictEqual(style.borderTopStyle, "none"); - assert.strictEqual(style.borderTopWidth, "1px"); + assert.strictEqual(style.borderTopWidth, "medium"); style.border = null; + style.borderImage = null; style.borderTopWidth = "1px"; assert.strictEqual(style.cssText, "border-top-width: 1px;"); style.borderStyle = "none"; assert.strictEqual(style.cssText, "border-top-width: 1px; border-style: none;"); assert.strictEqual(style.borderTopStyle, "none"); assert.strictEqual(style.borderTopWidth, "1px"); - style.border = null; + style.border = null; + style.borderImage = null; style.borderTopWidth = "1px"; assert.strictEqual(style.cssText, "border-top-width: 1px;"); style.borderTop = "none"; @@ -421,6 +421,7 @@ describe("CSSStyleDeclaration", () => { assert.strictEqual(style.borderTopWidth, "medium"); style.border = null; + style.borderImage = null; style.borderTopWidth = "1px"; assert.strictEqual(style.cssText, "border-top-width: 1px;"); style.borderTopStyle = "none"; @@ -429,10 +430,18 @@ describe("CSSStyleDeclaration", () => { assert.strictEqual(style.borderTopWidth, "1px"); }); + it("setting border to 1px should be okay", () => { + const style = new CSSStyleDeclaration(); + style.border = "1px"; + assert.strictEqual(style.cssText, "border: 1px;"); + assert.strictEqual(style.border, "1px"); + }); + it("setting border to 0 should be okay", () => { const style = new CSSStyleDeclaration(); style.border = 0; assert.strictEqual(style.cssText, "border: 0px;"); + assert.strictEqual(style.border, "0px"); }); it("setting borderColor to var() should be okay", () => { From b3e1bfe661a7e09763b2031b482872b9eb14a7d2 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Tue, 19 Aug 2025 08:11:05 +0900 Subject: [PATCH 37/41] Fix borders part2 --- lib/CSSStyleDeclaration.js | 145 ++++-- lib/parsers.js | 65 +-- lib/properties/border.js | 80 +-- lib/properties/borderBottom.js | 56 +- lib/properties/borderBottomColor.js | 17 +- lib/properties/borderBottomStyle.js | 17 +- lib/properties/borderBottomWidth.js | 17 +- lib/properties/borderColor.js | 67 ++- lib/properties/borderLeft.js | 56 +- lib/properties/borderLeftColor.js | 17 +- lib/properties/borderLeftStyle.js | 17 +- lib/properties/borderLeftWidth.js | 17 +- lib/properties/borderRight.js | 56 +- lib/properties/borderRightColor.js | 17 +- lib/properties/borderRightStyle.js | 17 +- lib/properties/borderRightWidth.js | 17 +- lib/properties/borderStyle.js | 67 ++- lib/properties/borderTop.js | 56 +- lib/properties/borderTopColor.js | 17 +- lib/properties/borderTopStyle.js | 17 +- lib/properties/borderTopWidth.js | 17 +- lib/properties/borderWidth.js | 67 ++- lib/properties/margin.js | 2 +- lib/shorthandProperties.js | 15 +- lib/utils/normalizeBorders.js | 772 ++++++++++++++++++++++++++++ test/CSSStyleDeclaration.test.js | 262 +++++++++- test/parsers.test.js | 2 +- 27 files changed, 1509 insertions(+), 463 deletions(-) create mode 100644 lib/utils/normalizeBorders.js diff --git a/lib/CSSStyleDeclaration.js b/lib/CSSStyleDeclaration.js index 396eef42..05688a9e 100644 --- a/lib/CSSStyleDeclaration.js +++ b/lib/CSSStyleDeclaration.js @@ -4,19 +4,25 @@ */ "use strict"; const allExtraProperties = require("./allExtraProperties"); -const { shorthandProperties } = require("./shorthandProperties"); const allProperties = require("./generated/allProperties"); const implementedProperties = require("./generated/implementedProperties"); const generatedProperties = require("./generated/properties"); const { hasVarFunc, + isValidPropertyValue, parseCSS, parsePropertyValue, parseShorthand, prepareValue, splitValue } = require("./parsers"); +const { shorthandProperties } = require("./shorthandProperties"); const { dashedToCamelCase } = require("./utils/camelize"); +const { + borderProperties, + normalizeBorderProperties, + prepareBorderProperties +} = require("./utils/normalizeBorders"); const { getPropertyDescriptor } = require("./utils/propertyDescriptors"); const { asciiLowercase } = require("./utils/strings"); @@ -124,7 +130,6 @@ class CSSStyleDeclaration { } } - // FIXME: get cssText() { if (this._computed) { return ""; @@ -133,24 +138,31 @@ class CSSStyleDeclaration { for (let i = 0; i < this._length; i++) { const property = this[i]; const value = this.getPropertyValue(property); - const priority = this.getPropertyPriority(property); + const priority = this._priorities.get(property); if (priority === "important") { - properties.set(property, `${property}: ${value} !${priority};`); + properties.set(property, { property, value, priority }); } else { - properties.set(property, `${property}: ${value};`); - } - } - for (const [property] of properties) { - if (shorthandProperties.has(property)) { - const longhandProperties = shorthandProperties.get(property); - for (const [longhand] of longhandProperties) { - if (properties.has(longhand) && !this.getPropertyPriority(longhand)) { - properties.delete(longhand); + if (shorthandProperties.has(property)) { + const longhandProperties = shorthandProperties.get(property); + for (const [longhand] of longhandProperties) { + if (properties.has(longhand) && !this._priorities.get(longhand)) { + properties.delete(longhand); + } } } + properties.set(property, { property, value, priority: null }); } } - return [...properties.values()].join(" "); + const normalizedProperties = normalizeBorderProperties(properties); + const parts = []; + for (const { property, value, priority } of normalizedProperties.values()) { + if (priority) { + parts.push(`${property}: ${value} !${priority};`); + } else { + parts.push(`${property}: ${value};`); + } + } + return parts.join(" "); } set cssText(val) { @@ -333,20 +345,37 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { /** * @param {string} property * @param {object} shorthandFor + * @param {Map} initialValues */ - value(property, shorthandFor) { - const values = []; + value(property, shorthandFor, initialValues = new Map()) { + const obj = {}; + const filter = initialValues.size > 0; + const firstKey = filter && initialValues.keys().next().value; for (const key of shorthandFor.keys()) { const val = this.getPropertyValue(key); - if (hasVarFunc(val)) { + if (val === "" || hasVarFunc(val)) { return ""; } - if (val !== "") { - values.push(val); + if (filter) { + const initialValue = initialValues.get(key); + if (key === firstKey) { + obj[key] = val; + } else if (val !== initialValue) { + obj[key] = val; + if (obj[firstKey] && obj[firstKey] === initialValues.get(firstKey)) { + delete obj[firstKey]; + } + } + } else { + obj[key] = val; } } - if (values.length) { - return values.join(" "); + if (Object.values(obj).length) { + const value = Object.values(obj).join(" "); + if (isValidPropertyValue(property, value)) { + return value; + } + return ""; } if (this._values.has(property)) { return this.getPropertyValue(property); @@ -358,13 +387,15 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { _implicitGetter: { /** - * @param {string} property + * @param {string} prefix + * @param {string} part * @param {Array.} positions */ - value(property, positions = []) { + value(prefix, part, positions = []) { + const suffix = part ? `-${part}` : ""; const values = []; for (const position of positions) { - const val = this.getPropertyValue(`${property}-${position}`); + const val = this.getPropertyValue(`${prefix}-${position}${suffix}`); if (val === "" || hasVarFunc(val)) { return ""; } @@ -498,16 +529,17 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { /** * @param {string} prefix * @param {string} part - * @param {string} val + * @param {string|Array.} val * @param {Function} parser * @param {Array.} positions */ value(prefix, part, val, parser, positions = []) { const suffix = part ? `-${part}` : ""; - const shorthandProp = `${prefix}${suffix}`; const values = []; if (val === "") { values.push(val); + } else if (Array.isArray(val) && val.length) { + values.push(...val); } else { const parsedValue = parser(val, { globalObject: this._global @@ -520,29 +552,43 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { if (!values.length || values.length > positions.length) { return; } - this._setProperty(shorthandProp, values.join(" ")); + const shorthandProp = `${prefix}${suffix}`; + const shorthandVal = values.join(" "); + const positionValues = [...values]; switch (positions.length) { - case 4: + case 4: { if (values.length === 1) { - values.push(values[0], values[0], values[0]); + positionValues.push(values[0], values[0], values[0]); } else if (values.length === 2) { - values.push(values[0], values[1]); + positionValues.push(values[0], values[1]); } else if (values.length === 3) { - values.push(values[1]); + positionValues.push(values[1]); } break; - case 2: + } + case 2: { if (values.length === 1) { - values.push(values[0]); + positionValues.push(values[0]); } break; + } default: } + const longhandValues = []; + for (const position of positions) { + const property = `${prefix}-${position}${suffix}`; + const longhandValue = this.getPropertyValue(property); + const longhandPriority = this._priorities.get(property); + longhandValues.push([longhandValue, longhandPriority]); + } for (let i = 0; i < positions.length; i++) { const property = `${prefix}-${positions[i]}${suffix}`; + const [longhandValue, longhandPriority] = longhandValues[i]; + const longhandVal = longhandPriority ? longhandValue : positionValues[i]; this.removeProperty(property); - this._values.set(property, values[i]); + this._values.set(property, longhandVal); } + this._setProperty(shorthandProp, shorthandVal); }, enumerable: false }, @@ -568,13 +614,13 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { } const property = `${prefix}-${part}`; this._setProperty(property, parsedValue); - const combinedPriority = this.getPropertyPriority(prefix); + const combinedPriority = this._priorities.get(prefix); const subparts = []; for (const position of positions) { subparts.push(`${prefix}-${position}`); } const parts = subparts.map((subpart) => this._values.get(subpart)); - const priorities = subparts.map((subpart) => this.getPropertyPriority(subpart)); + const priorities = subparts.map((subpart) => this._priorities.get(subpart)); const [priority] = priorities; // Combine into a single property if all values are set and have the same // priority. @@ -599,6 +645,33 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { } }, enumerable: false + }, + + _implicitBorderSetter: { + /** + * @param {string} prop + * @param {string} val + */ + value(prop, val) { + const properties = new Map(); + if (prop !== "border") { + for (let i = 0; i < this._length; i++) { + const property = this[i]; + if (borderProperties.has(property)) { + const value = this.getPropertyValue(property); + properties.set(property, { property, value, priority: null }); + } + } + } + const parsedProperties = prepareBorderProperties(prop, val, properties, { + globalObject: this._global + }); + for (const [property, item] of parsedProperties) { + const { value } = item; + this._setProperty(property, value); + } + }, + enumerable: false } }); diff --git a/lib/parsers.js b/lib/parsers.js index 6e8de875..e4b23746 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -158,6 +158,39 @@ exports.isValidPropertyValue = function isValidPropertyValue(prop, val) { return error === null && matched !== null; }; +// Parse CSS function to AST. +exports.parseFunction = function parseFunction(val) { + if (typeof val !== "string") { + val = exports.prepareValue(val); + } + if (val === "") { + return { + name: null, + value: "", + hasVar: false, + raw: "" + }; + } + const obj = exports.parseCSS(val, { context: "value" }, true); + if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { + return; + } + const [{ name, type }] = obj.children; + if (type !== "Function") { + return; + } + const value = val + .replace(new RegExp(`^${name}\\(`), "") + .replace(/\)$/, "") + .trim(); + return { + name, + value, + hasVar: exports.hasVarFunc(val), + raw: val + }; +}; + exports.parseCalc = function parseCalc(val, opt = { format: "specifiedValue" }) { if (typeof val !== "string") { val = exports.prepareValue(val); @@ -193,38 +226,6 @@ exports.parseCalc = function parseCalc(val, opt = { format: "specifiedValue" }) return values.join(" "); }; -exports.parseFunction = function parseFunction(val) { - if (typeof val !== "string") { - val = exports.prepareValue(val); - } - if (val === "") { - return { - name: null, - value: "", - hasVar: false, - raw: "" - }; - } - const obj = exports.parseCSS(val, { context: "value" }, true); - if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { - return; - } - const [{ name, type }] = obj.children; - if (type !== "Function") { - return; - } - const value = val - .replace(new RegExp(`^${name}\\(`), "") - .replace(/\)$/, "") - .trim(); - return { - name, - value, - hasVar: exports.hasVarFunc(val), - raw: val - }; -}; - exports.parseNumber = function parseNumber(val, opt = {}) { const { clamp } = opt; const max = opt.max ?? Number.INFINITY; diff --git a/lib/properties/border.js b/lib/properties/border.js index 2e34f245..5682c61f 100644 --- a/lib/properties/border.js +++ b/lib/properties/border.js @@ -9,28 +9,28 @@ const borderRight = require("./borderRight"); const borderBottom = require("./borderBottom"); const borderLeft = require("./borderLeft"); -const initialValues = new Map([ +module.exports.initialValues = new Map([ ["border-width", "medium"], ["border-style", "none"], ["border-color", "currentcolor"] ]); -const positionShorthandFor = new Map([ - ["border-top", borderTop], - ["border-right", borderRight], - ["border-bottom", borderBottom], - ["border-left", borderLeft] -]); - module.exports.shorthandFor = new Map([ ["border-width", borderWidth], ["border-style", borderStyle], ["border-color", borderColor] ]); +module.exports.positionShorthandFor = new Map([ + ["border-top", borderTop], + ["border-right", borderRight], + ["border-bottom", borderBottom], + ["border-left", borderLeft] +]); + module.exports.parse = function parse(v, opt = {}) { const { globalObject } = opt; - if (v === "") { + if (v === "" || parsers.hasVarFunc(v)) { return v; } const values = parsers.splitValue(v); @@ -76,13 +76,7 @@ module.exports.parse = function parse(v, opt = {}) { break; } case "GlobalKeyword": { - if (values.length !== 1) { - return; - } - for (const key of module.exports.shorthandFor.keys()) { - parsedValues.set(key, name); - } - break; + return name; } case "Hash": { if (parsedValues.has("border-color")) { @@ -127,10 +121,18 @@ module.exports.parse = function parse(v, opt = {}) { } if (parsedValues.size) { const keys = module.exports.shorthandFor.keys(); - const obj = {}; + const obj = { + "border-width": "medium" + }; for (const key of keys) { if (parsedValues.has(key)) { - obj[key] = parsedValues.get(key); + const parsedValue = parsedValues.get(key); + if (parsedValue !== module.exports.initialValues.get(key)) { + obj[key] = parsedValues.get(key); + if (obj["border-width"] && obj["border-width"] === "medium") { + delete obj["border-width"]; + } + } } } return obj; @@ -141,47 +143,13 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - for (const [key] of module.exports.shorthandFor) { - this._setProperty(key, ""); - } - for (const [key, parser] of positionShorthandFor) { - this._setProperty(key, ""); - for (const [subkey] of parser.shorthandFor) { - this._setProperty(subkey, ""); - } - } - this._setProperty("border", v); - this._setProperty("border-image", "none"); + this._implicitBorderSetter("border", v); } else { - const obj = module.exports.parse(v, { + const val = module.exports.parse(v, { globalObject: this._global }); - if (obj === "") { - for (const [key] of module.exports.shorthandFor) { - this._setProperty(key, ""); - } - for (const [key, parser] of positionShorthandFor) { - this._setProperty(key, ""); - for (const [subkey] of parser.shorthandFor) { - this._setProperty(subkey, ""); - } - } - this._setProperty("border", ""); - this._setProperty("border-image", ""); - } else if (obj) { - const valueObj = Object.fromEntries(initialValues); - for (const key of Object.keys(obj)) { - valueObj[key] = obj[key]; - } - const positions = ["top", "right", "bottom", "left"]; - for (const [key, value] of Object.entries(valueObj)) { - this._setProperty(key, value); - const [prefix, suffix] = key.split("-"); - const { parse: parser } = module.exports.shorthandFor.get(key); - this._implicitSetter(prefix, suffix, value, parser, positions); - } - this._setProperty("border", [...Object.values(obj)].join(" ")); - this._setProperty("border-image", "none"); + if (val || typeof val === "string") { + this._implicitBorderSetter("border", val); } } }, diff --git a/lib/properties/borderBottom.js b/lib/properties/borderBottom.js index 1bf00361..b39e7dc7 100644 --- a/lib/properties/borderBottom.js +++ b/lib/properties/borderBottom.js @@ -65,13 +65,7 @@ module.exports.parse = function parse(v, opt = {}) { break; } case "GlobalKeyword": { - if (values.length !== 1) { - return; - } - for (const key of module.exports.shorthandFor.keys()) { - parsedValues.set(key, name); - } - break; + return name; } case "Hash": { if (parsedValues.has("border-bottom-color")) { @@ -116,10 +110,18 @@ module.exports.parse = function parse(v, opt = {}) { } if (parsedValues.size) { const keys = module.exports.shorthandFor.keys(); - const obj = {}; + const obj = { + "border-bottom-width": "medium" + }; for (const key of keys) { if (parsedValues.has(key)) { - obj[key] = parsedValues.get(key); + const parsedValue = parsedValues.get(key); + if (parsedValue !== initialValues.get(key)) { + obj[key] = parsedValues.get(key); + if (obj["border-bottom-width"] && obj["border-bottom-width"] === "medium") { + delete obj["border-bottom-width"]; + } + } } } return obj; @@ -130,44 +132,18 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - for (const [key] of module.exports.shorthandFor) { - this._setProperty(key, ""); - } - this._setProperty("border", ""); - this._setProperty("border-bottom", v); + this._implicitBorderSetter("border-bottom", v); } else { - const obj = module.exports.parse(v, { + const val = module.exports.parse(v, { globalObject: this._global }); - if (obj === "") { - for (const [key] of module.exports.shorthandFor) { - this._setProperty(key, ""); - } - this._setProperty("border", ""); - this._setProperty("border-bottom", ""); - } else if (obj) { - const valueObj = Object.fromEntries(initialValues); - for (const key of Object.keys(obj)) { - valueObj[key] = obj[key]; - } - for (const [key, value] of Object.entries(valueObj)) { - this._setProperty(key, value); - } - this._setProperty("border", ""); - this._setProperty("border-bottom", [...Object.values(obj)].join(" ")); + if (val || typeof val === "string") { + this._implicitBorderSetter("border-bottom", val); } } }, get() { - const val = this.getPropertyValue("border-bottom"); - if (parsers.isGlobalKeyword(val) || parsers.hasVarFunc(val)) { - return val; - } - const subVal = this._shorthandGetter("border-bottom", module.exports.shorthandFor); - if (parsers.hasVarFunc(subVal)) { - return ""; - } - return subVal; + return this.getPropertyValue("border-bottom"); }, enumerable: true, configurable: true diff --git a/lib/properties/borderBottomColor.js b/lib/properties/borderBottomColor.js index 18039f03..0c4e6f7c 100644 --- a/lib/properties/borderBottomColor.js +++ b/lib/properties/borderBottomColor.js @@ -30,17 +30,14 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._setProperty("border", ""); - this._setProperty("border-color", ""); - this._setProperty("border-bottom", ""); - this._setProperty("border-bottom-color", v); + this._implicitBorderSetter("border-bottom-color", v); } else { - this._setProperty( - "border-bottom-color", - module.exports.parse(v, { - globalObject: this._global - }) - ); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (typeof val === "string") { + this._implicitBorderSetter("border-bottom-color", val); + } } }, get() { diff --git a/lib/properties/borderBottomStyle.js b/lib/properties/borderBottomStyle.js index 4722659a..bba0f584 100644 --- a/lib/properties/borderBottomStyle.js +++ b/lib/properties/borderBottomStyle.js @@ -29,17 +29,14 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._setProperty("border", ""); - this._setProperty("border-bottom", ""); - this._setProperty("border-style", ""); - this._setProperty("border-bottom-style", v); + this._implicitBorderSetter("border-bottom-style", v); } else { - this._setProperty( - "border-bottom-style", - module.exports.parse(v, { - globalObject: this._global - }) - ); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (typeof val === "string") { + this._implicitBorderSetter("border-bottom-style", val); + } } }, get() { diff --git a/lib/properties/borderBottomWidth.js b/lib/properties/borderBottomWidth.js index 189dec83..992b9575 100644 --- a/lib/properties/borderBottomWidth.js +++ b/lib/properties/borderBottomWidth.js @@ -39,17 +39,14 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._setProperty("border", ""); - this._setProperty("border-width", ""); - this._setProperty("border-bottom", ""); - this._setProperty("border-bottom-width", v); + this._implicitBorderSetter("border-bottom-width", v); } else { - this._setProperty( - "border-bottom-width", - module.exports.parse(v, { - globalObject: this._global - }) - ); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (typeof val === "string") { + this._implicitBorderSetter("border-bottom-width", val); + } } }, get() { diff --git a/lib/properties/borderColor.js b/lib/properties/borderColor.js index 8ac11cc8..0d1e6322 100644 --- a/lib/properties/borderColor.js +++ b/lib/properties/borderColor.js @@ -1,6 +1,17 @@ "use strict"; const parsers = require("../parsers"); +const borderTopColor = require("./borderTopColor"); +const borderRightColor = require("./borderRightColor"); +const borderBottomColor = require("./borderBottomColor"); +const borderLeftColor = require("./borderLeftColor"); + +module.exports.shorthandFor = new Map([ + ["border-top-color", borderTopColor], + ["border-right-color", borderRightColor], + ["border-bottom-color", borderBottomColor], + ["border-left-color", borderLeftColor] +]); module.exports.parse = function parse(v, opt = {}) { const { globalObject } = opt; @@ -23,8 +34,7 @@ module.exports.parse = function parse(v, opt = {}) { if (values.length !== 1) { return; } - parsedValues.push(name); - break; + return name; } default: { const parsedValue = parsers.parseColor([value]); @@ -39,7 +49,42 @@ module.exports.parse = function parse(v, opt = {}) { parsedValues.push(values); } if (parsedValues.length) { - return parsedValues.join(" "); + switch (parsedValues.length) { + case 1: { + return parsedValues; + } + case 2: { + const [val1, val2] = parsedValues; + if (val1 === val2) { + return [val1]; + } + return parsedValues; + } + case 3: { + const [val1, val2, val3] = parsedValues; + if (val1 === val3) { + if (val1 === val2) { + return [val1]; + } + return [val1, val2]; + } + return parsedValues; + } + case 4: { + const [val1, val2, val3, val4] = parsedValues; + if (val2 === val4) { + if (val1 === val3) { + if (val1 === val2) { + return [val1]; + } + return [val1, val2]; + } + return [val1, val2, val3]; + } + return parsedValues; + } + default: + } } }; @@ -47,16 +92,14 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._setProperty("border", ""); - this._setProperty("border-color", v); + this._implicitBorderSetter("border-color", v); } else { - this._setProperty("border", ""); - this._implicitSetter("border", "color", v, module.exports.parse, [ - "top", - "right", - "bottom", - "left" - ]); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (Array.isArray(val) || typeof val === "string") { + this._implicitBorderSetter("border-color", val); + } } }, get() { diff --git a/lib/properties/borderLeft.js b/lib/properties/borderLeft.js index cf15a627..4e80ebf0 100644 --- a/lib/properties/borderLeft.js +++ b/lib/properties/borderLeft.js @@ -65,13 +65,7 @@ module.exports.parse = function parse(v, opt = {}) { break; } case "GlobalKeyword": { - if (values.length !== 1) { - return; - } - for (const key of module.exports.shorthandFor.keys()) { - parsedValues.set(key, name); - } - break; + return name; } case "Hash": { if (parsedValues.has("border-left-color")) { @@ -116,10 +110,18 @@ module.exports.parse = function parse(v, opt = {}) { } if (parsedValues.size) { const keys = module.exports.shorthandFor.keys(); - const obj = {}; + const obj = { + "border-left-width": "medium" + }; for (const key of keys) { if (parsedValues.has(key)) { - obj[key] = parsedValues.get(key); + const parsedValue = parsedValues.get(key); + if (parsedValue !== initialValues.get(key)) { + obj[key] = parsedValues.get(key); + if (obj["border-left-width"] && obj["border-left-width"] === "medium") { + delete obj["border-left-width"]; + } + } } } return obj; @@ -130,44 +132,18 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - for (const [key] of module.exports.shorthandFor) { - this._setProperty(key, ""); - } - this._setProperty("border", ""); - this._setProperty("border-left", v); + this._implicitBorderSetter("border-left", v); } else { - const obj = module.exports.parse(v, { + const val = module.exports.parse(v, { globalObject: this._global }); - if (obj === "") { - for (const [key] of module.exports.shorthandFor) { - this._setProperty(key, ""); - } - this._setProperty("border", ""); - this._setProperty("border-left", ""); - } else if (obj) { - const valueObj = Object.fromEntries(initialValues); - for (const key of Object.keys(obj)) { - valueObj[key] = obj[key]; - } - for (const [key, value] of Object.entries(valueObj)) { - this._setProperty(key, value); - } - this._setProperty("border", ""); - this._setProperty("border-left", [...Object.values(obj)].join(" ")); + if (val || typeof val === "string") { + this._implicitBorderSetter("border-left", val); } } }, get() { - const val = this.getPropertyValue("border-left"); - if (parsers.isGlobalKeyword(val) || parsers.hasVarFunc(val)) { - return val; - } - const subVal = this._shorthandGetter("border-left", module.exports.shorthandFor); - if (parsers.hasVarFunc(subVal)) { - return ""; - } - return subVal; + return this.getPropertyValue("border-left"); }, enumerable: true, configurable: true diff --git a/lib/properties/borderLeftColor.js b/lib/properties/borderLeftColor.js index a4c9768e..001189cf 100644 --- a/lib/properties/borderLeftColor.js +++ b/lib/properties/borderLeftColor.js @@ -30,17 +30,14 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._setProperty("border", ""); - this._setProperty("border-color", ""); - this._setProperty("border-left", ""); - this._setProperty("border-left-color", v); + this._implicitBorderSetter("border-left-color", v); } else { - this._setProperty( - "border-left-color", - module.exports.parse(v, { - globalObject: this._global - }) - ); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (typeof val === "string") { + this._implicitBorderSetter("border-left-color", val); + } } }, get() { diff --git a/lib/properties/borderLeftStyle.js b/lib/properties/borderLeftStyle.js index e9dc510b..00cb6a02 100644 --- a/lib/properties/borderLeftStyle.js +++ b/lib/properties/borderLeftStyle.js @@ -29,17 +29,14 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._setProperty("border", ""); - this._setProperty("border-left", ""); - this._setProperty("border-style", ""); - this._setProperty("border-left-style", v); + this._implicitBorderSetter("border-left-style", v); } else { - this._setProperty( - "border-left-style", - module.exports.parse(v, { - globalObject: this._global - }) - ); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (typeof val === "string") { + this._implicitBorderSetter("border-left-style", val); + } } }, get() { diff --git a/lib/properties/borderLeftWidth.js b/lib/properties/borderLeftWidth.js index ba6d0c88..8b69ecd5 100644 --- a/lib/properties/borderLeftWidth.js +++ b/lib/properties/borderLeftWidth.js @@ -39,17 +39,14 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._setProperty("border", ""); - this._setProperty("border-width", ""); - this._setProperty("border-left", ""); - this._setProperty("border-left-width", v); + this._implicitBorderSetter("border-left-width", v); } else { - this._setProperty( - "border-left-width", - module.exports.parse(v, { - globalObject: this._global - }) - ); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (typeof val === "string") { + this._implicitBorderSetter("border-left-width", val); + } } }, get() { diff --git a/lib/properties/borderRight.js b/lib/properties/borderRight.js index 49132a6a..2b6d4d00 100644 --- a/lib/properties/borderRight.js +++ b/lib/properties/borderRight.js @@ -65,13 +65,7 @@ module.exports.parse = function parse(v, opt = {}) { break; } case "GlobalKeyword": { - if (values.length !== 1) { - return; - } - for (const key of module.exports.shorthandFor.keys()) { - parsedValues.set(key, name); - } - break; + return name; } case "Hash": { if (parsedValues.has("border-right-color")) { @@ -116,10 +110,18 @@ module.exports.parse = function parse(v, opt = {}) { } if (parsedValues.size) { const keys = module.exports.shorthandFor.keys(); - const obj = {}; + const obj = { + "border-right-width": "medium" + }; for (const key of keys) { if (parsedValues.has(key)) { - obj[key] = parsedValues.get(key); + const parsedValue = parsedValues.get(key); + if (parsedValue !== initialValues.get(key)) { + obj[key] = parsedValues.get(key); + if (obj["border-right-width"] && obj["border-right-width"] === "medium") { + delete obj["border-right-width"]; + } + } } } return obj; @@ -130,44 +132,18 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - for (const [key] of module.exports.shorthandFor) { - this._setProperty(key, ""); - } - this._setProperty("border", ""); - this._setProperty("border-right", v); + this._implicitBorderSetter("border-right", v); } else { - const obj = module.exports.parse(v, { + const val = module.exports.parse(v, { globalObject: this._global }); - if (obj === "") { - for (const [key] of module.exports.shorthandFor) { - this._setProperty(key, ""); - } - this._setProperty("border", ""); - this._setProperty("border-right", ""); - } else if (obj) { - const valueObj = Object.fromEntries(initialValues); - for (const key of Object.keys(obj)) { - valueObj[key] = obj[key]; - } - for (const [key, value] of Object.entries(valueObj)) { - this._setProperty(key, value); - } - this._setProperty("border", ""); - this._setProperty("border-right", [...Object.values(obj)].join(" ")); + if (val || typeof val === "string") { + this._implicitBorderSetter("border-right", val); } } }, get() { - const val = this.getPropertyValue("border-right"); - if (parsers.isGlobalKeyword(val) || parsers.hasVarFunc(val)) { - return val; - } - const subVal = this._shorthandGetter("border-right", module.exports.shorthandFor); - if (parsers.hasVarFunc(subVal)) { - return ""; - } - return subVal; + return this.getPropertyValue("border-right"); }, enumerable: true, configurable: true diff --git a/lib/properties/borderRightColor.js b/lib/properties/borderRightColor.js index 950e7fc5..871ce723 100644 --- a/lib/properties/borderRightColor.js +++ b/lib/properties/borderRightColor.js @@ -30,17 +30,14 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._setProperty("border", ""); - this._setProperty("border-color", ""); - this._setProperty("border-right", ""); - this._setProperty("border-right-color", v); + this._implicitBorderSetter("border-right-color", v); } else { - this._setProperty( - "border-right-color", - module.exports.parse(v, { - globalObject: this._global - }) - ); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (typeof val === "string") { + this._implicitBorderSetter("border-right-color", val); + } } }, get() { diff --git a/lib/properties/borderRightStyle.js b/lib/properties/borderRightStyle.js index 8220ea11..19606d40 100644 --- a/lib/properties/borderRightStyle.js +++ b/lib/properties/borderRightStyle.js @@ -29,17 +29,14 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._setProperty("border", ""); - this._setProperty("border-right", ""); - this._setProperty("border-style", ""); - this._setProperty("border-right-style", v); + this._implicitBorderSetter("border-right-style", v); } else { - this._setProperty( - "border-right-style", - module.exports.parse(v, { - globalObject: this._global - }) - ); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (typeof val === "string") { + this._implicitBorderSetter("border-right-style", val); + } } }, get() { diff --git a/lib/properties/borderRightWidth.js b/lib/properties/borderRightWidth.js index ba7b798d..56245916 100644 --- a/lib/properties/borderRightWidth.js +++ b/lib/properties/borderRightWidth.js @@ -39,17 +39,14 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._setProperty("border", ""); - this._setProperty("border-width", ""); - this._setProperty("border-right", ""); - this._setProperty("border-right-width", v); + this._implicitBorderSetter("border-right-width", v); } else { - this._setProperty( - "border-right-width", - module.exports.parse(v, { - globalObject: this._global - }) - ); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (typeof val === "string") { + this._implicitBorderSetter("border-right-width", val); + } } }, get() { diff --git a/lib/properties/borderStyle.js b/lib/properties/borderStyle.js index 09980b4f..b6bf1eba 100644 --- a/lib/properties/borderStyle.js +++ b/lib/properties/borderStyle.js @@ -1,6 +1,17 @@ "use strict"; const parsers = require("../parsers"); +const borderTopStyle = require("./borderTopStyle"); +const borderRightStyle = require("./borderRightStyle"); +const borderBottomStyle = require("./borderBottomStyle"); +const borderLeftStyle = require("./borderLeftStyle"); + +module.exports.shorthandFor = new Map([ + ["border-top-style", borderTopStyle], + ["border-right-style", borderRightStyle], + ["border-bottom-style", borderBottomStyle], + ["border-left-style", borderLeftStyle] +]); module.exports.parse = function parse(v, opt = {}) { const { globalObject } = opt; @@ -23,8 +34,7 @@ module.exports.parse = function parse(v, opt = {}) { if (values.length !== 1) { return; } - parsedValues.push(name); - break; + return name; } case "Identifier": { parsedValues.push(name); @@ -39,7 +49,42 @@ module.exports.parse = function parse(v, opt = {}) { parsedValues.push(values); } if (parsedValues.length) { - return parsedValues.join(" "); + switch (parsedValues.length) { + case 1: { + return parsedValues; + } + case 2: { + const [val1, val2] = parsedValues; + if (val1 === val2) { + return [val1]; + } + return parsedValues; + } + case 3: { + const [val1, val2, val3] = parsedValues; + if (val1 === val3) { + if (val1 === val2) { + return [val1]; + } + return [val1, val2]; + } + return parsedValues; + } + case 4: { + const [val1, val2, val3, val4] = parsedValues; + if (val2 === val4) { + if (val1 === val3) { + if (val1 === val2) { + return [val1]; + } + return [val1, val2]; + } + return [val1, val2, val3]; + } + return parsedValues; + } + default: + } } }; @@ -47,16 +92,14 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._setProperty("border", ""); - this._setProperty("border-style", v); + this._implicitBorderSetter("border-style", v); } else { - this._setProperty("border", ""); - this._implicitSetter("border", "style", v, module.exports.parse, [ - "top", - "right", - "bottom", - "left" - ]); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (Array.isArray(val) || typeof val === "string") { + this._implicitBorderSetter("border-style", val); + } } }, get() { diff --git a/lib/properties/borderTop.js b/lib/properties/borderTop.js index 091ae1b3..48a7ad4a 100644 --- a/lib/properties/borderTop.js +++ b/lib/properties/borderTop.js @@ -65,13 +65,7 @@ module.exports.parse = function parse(v, opt = {}) { break; } case "GlobalKeyword": { - if (values.length !== 1) { - return; - } - for (const key of module.exports.shorthandFor.keys()) { - parsedValues.set(key, name); - } - break; + return name; } case "Hash": { if (parsedValues.has("border-top-color")) { @@ -116,10 +110,18 @@ module.exports.parse = function parse(v, opt = {}) { } if (parsedValues.size) { const keys = module.exports.shorthandFor.keys(); - const obj = {}; + const obj = { + "border-top-width": "medium" + }; for (const key of keys) { if (parsedValues.has(key)) { - obj[key] = parsedValues.get(key); + const parsedValue = parsedValues.get(key); + if (parsedValue !== initialValues.get(key)) { + obj[key] = parsedValues.get(key); + if (obj["border-top-width"] && obj["border-top-width"] === "medium") { + delete obj["border-top-width"]; + } + } } } return obj; @@ -130,44 +132,18 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - for (const [key] of module.exports.shorthandFor) { - this._setProperty(key, ""); - } - this._setProperty("border", ""); - this._setProperty("border-top", v); + this._implicitBorderSetter("border-top", v); } else { - const obj = module.exports.parse(v, { + const val = module.exports.parse(v, { globalObject: this._global }); - if (obj === "") { - for (const [key] of module.exports.shorthandFor) { - this._setProperty(key, ""); - } - this._setProperty("border", ""); - this._setProperty("border-top", ""); - } else if (obj) { - const valueObj = Object.fromEntries(initialValues); - for (const key of Object.keys(obj)) { - valueObj[key] = obj[key]; - } - for (const [key, value] of Object.entries(valueObj)) { - this._setProperty(key, value); - } - this._setProperty("border", ""); - this._setProperty("border-top", [...Object.values(obj)].join(" ")); + if (val || typeof val === "string") { + this._implicitBorderSetter("border-top", val); } } }, get() { - const val = this.getPropertyValue("border-top"); - if (parsers.isGlobalKeyword(val) || parsers.hasVarFunc(val)) { - return val; - } - const subVal = this._shorthandGetter("border-top", module.exports.shorthandFor); - if (parsers.hasVarFunc(subVal)) { - return ""; - } - return subVal; + return this.getPropertyValue("border-top"); }, enumerable: true, configurable: true diff --git a/lib/properties/borderTopColor.js b/lib/properties/borderTopColor.js index b9116846..4cc53921 100644 --- a/lib/properties/borderTopColor.js +++ b/lib/properties/borderTopColor.js @@ -30,17 +30,14 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._setProperty("border", ""); - this._setProperty("border-color", ""); - this._setProperty("border-top", ""); - this._setProperty("border-top-color", v); + this._implicitBorderSetter("border-top-color", v); } else { - this._setProperty( - "border-top-color", - module.exports.parse(v, { - globalObject: this._global - }) - ); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (typeof val === "string") { + this._implicitBorderSetter("border-top-color", val); + } } }, get() { diff --git a/lib/properties/borderTopStyle.js b/lib/properties/borderTopStyle.js index f2dae236..f30577ee 100644 --- a/lib/properties/borderTopStyle.js +++ b/lib/properties/borderTopStyle.js @@ -29,17 +29,14 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._setProperty("border", ""); - this._setProperty("border-top", ""); - this._setProperty("border-style", ""); - this._setProperty("border-top-style", v); + this._implicitBorderSetter("border-top-style", v); } else { - this._setProperty( - "border-top-style", - module.exports.parse(v, { - globalObject: this._global - }) - ); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (typeof val === "string") { + this._implicitBorderSetter("border-top-style", val); + } } }, get() { diff --git a/lib/properties/borderTopWidth.js b/lib/properties/borderTopWidth.js index fd9a20e2..55864023 100644 --- a/lib/properties/borderTopWidth.js +++ b/lib/properties/borderTopWidth.js @@ -39,17 +39,14 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._setProperty("border", ""); - this._setProperty("border-width", ""); - this._setProperty("border-top", ""); - this._setProperty("border-top-width", v); + this._implicitBorderSetter("border-top-width", v); } else { - this._setProperty( - "border-top-width", - module.exports.parse(v, { - globalObject: this._global - }) - ); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (typeof val === "string") { + this._implicitBorderSetter("border-top-width", val); + } } }, get() { diff --git a/lib/properties/borderWidth.js b/lib/properties/borderWidth.js index 06cc226c..f0c6661d 100644 --- a/lib/properties/borderWidth.js +++ b/lib/properties/borderWidth.js @@ -1,6 +1,17 @@ "use strict"; const parsers = require("../parsers"); +const borderTopWidth = require("./borderTopWidth"); +const borderRightWidth = require("./borderRightWidth"); +const borderBottomWidth = require("./borderBottomWidth"); +const borderLeftWidth = require("./borderLeftWidth"); + +module.exports.shorthandFor = new Map([ + ["border-top-width", borderTopWidth], + ["border-right-width", borderRightWidth], + ["border-bottom-width", borderBottomWidth], + ["border-left-width", borderLeftWidth] +]); module.exports.parse = function parse(v, opt = {}) { const { globalObject } = opt; @@ -30,8 +41,7 @@ module.exports.parse = function parse(v, opt = {}) { if (values.length !== 1) { return; } - parsedValues.push(name); - break; + return name; } case "Identifier": { parsedValues.push(name); @@ -52,7 +62,42 @@ module.exports.parse = function parse(v, opt = {}) { parsedValues.push(values); } if (parsedValues.length) { - return parsedValues.join(" "); + switch (parsedValues.length) { + case 1: { + return parsedValues; + } + case 2: { + const [val1, val2] = parsedValues; + if (val1 === val2) { + return [val1]; + } + return parsedValues; + } + case 3: { + const [val1, val2, val3] = parsedValues; + if (val1 === val3) { + if (val1 === val2) { + return [val1]; + } + return [val1, val2]; + } + return parsedValues; + } + case 4: { + const [val1, val2, val3, val4] = parsedValues; + if (val2 === val4) { + if (val1 === val3) { + if (val1 === val2) { + return [val1]; + } + return [val1, val2]; + } + return [val1, val2, val3]; + } + return parsedValues; + } + default: + } } }; @@ -60,16 +105,14 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._setProperty("border", ""); - this._setProperty("border-width", v); + this._implicitBorderSetter("border-width", v); } else { - this._implicitSetter("border", "width", v, module.exports.parse, [ - "top", - "right", - "bottom", - "left" - ]); - this._setProperty("border", ""); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (Array.isArray(val) || typeof val === "string") { + this._implicitBorderSetter("border-width", val); + } } }, get() { diff --git a/lib/properties/margin.js b/lib/properties/margin.js index 3f87dbce..dad882f2 100644 --- a/lib/properties/margin.js +++ b/lib/properties/margin.js @@ -67,7 +67,7 @@ module.exports.definition = { } }, get() { - const val = this._implicitGetter("margin", positions); + const val = this._implicitGetter("margin", "", positions); if (val === "") { return this.getPropertyValue("margin"); } diff --git a/lib/shorthandProperties.js b/lib/shorthandProperties.js index 4b6b59da..61b11973 100644 --- a/lib/shorthandProperties.js +++ b/lib/shorthandProperties.js @@ -2,6 +2,9 @@ const background = require("./properties/background"); const border = require("./properties/border"); +const borderWidth = require("./properties/borderWidth"); +const borderStyle = require("./properties/borderStyle"); +const borderColor = require("./properties/borderColor"); const borderTop = require("./properties/borderTop"); const borderRight = require("./properties/borderRight"); const borderBottom = require("./properties/borderBottom"); @@ -13,15 +16,11 @@ module.exports.shorthandProperties = new Map([ ["background", background.shorthandFor], [ "border", - new Map([ - ...border.shorthandFor, - ...borderTop.shorthandFor, - ...borderRight.shorthandFor, - ...borderBottom.shorthandFor, - ...borderLeft.shorthandFor, - ["border-image", null] - ]) + new Map([...border.shorthandFor, ...border.positionShorthandFor, ["border-image", null]]) ], + ["border-width", borderWidth.shorthandFor], + ["border-style", borderStyle.shorthandFor], + ["border-color", borderColor.shorthandFor], ["border-top", borderTop.shorthandFor], ["border-right", borderRight.shorthandFor], ["border-bottom", borderBottom.shorthandFor], diff --git a/lib/utils/normalizeBorders.js b/lib/utils/normalizeBorders.js new file mode 100644 index 00000000..2aa56ab7 --- /dev/null +++ b/lib/utils/normalizeBorders.js @@ -0,0 +1,772 @@ +"use strict"; + +const { hasVarFunc, isGlobalKeyword, isValidPropertyValue, splitValue } = require("../parsers"); +const border = require("../properties/border"); +const borderTop = require("../properties/borderTop"); +const borderRight = require("../properties/borderRight"); +const borderBottom = require("../properties/borderBottom"); +const borderLeft = require("../properties/borderLeft"); + +module.exports.borderProperties = new Set([ + "border", + "border-image", + ...border.shorthandFor.keys(), + ...border.positionShorthandFor.keys(), + ...borderTop.shorthandFor.keys(), + ...borderRight.shorthandFor.keys(), + ...borderBottom.shorthandFor.keys(), + ...borderLeft.shorthandFor.keys() +]); + +const borderElements = { + name: "border", + positions: ["top", "right", "bottom", "left"], + lines: ["width", "style", "color"] +}; +const firstInitialKey = border.initialValues.keys().next().value; +const firstInitialValue = border.initialValues.get(firstInitialKey); + +const getPropertyItem = (property, properties) => { + const propertyItem = properties.get(property) ?? { + property, + value: "", + priority: null + }; + return propertyItem; +}; + +const matchesShorthandValue = (property, value, shorthandValue, opt = {}) => { + const { globalObject } = opt; + const obj = border.parse(shorthandValue, { + globalObject + }); + if (Object.hasOwn(obj, property)) { + return value === obj[property]; + } + return value === border.initialValues.get(property); +}; + +const replaceShorthandValue = (value, shorthandValue, opt = {}) => { + const { globalObject } = opt; + const valueObj = border.parse(value, { + globalObject + }); + const shorthandObj = shorthandValue + ? border.parse(shorthandValue, { + globalObject + }) + : { + [firstInitialKey]: firstInitialValue + }; + const keys = border.shorthandFor.keys(); + for (const key of keys) { + const initialValue = border.initialValues.get(key); + let parsedValue = initialValue; + if (Object.hasOwn(valueObj, key)) { + parsedValue = valueObj[key]; + } + if (parsedValue === initialValue) { + if (key === firstInitialKey) { + if (!Object.hasOwn(shorthandObj, key)) { + shorthandObj[key] = parsedValue; + } + } else { + delete shorthandObj[key]; + } + } else { + shorthandObj[key] = parsedValue; + if (shorthandObj[firstInitialKey] && shorthandObj[firstInitialKey] === firstInitialValue) { + delete shorthandObj[firstInitialKey]; + } + } + } + return Object.values(shorthandObj).join(" "); +}; + +const replaceLineValue = (value, lineValue, position) => { + const values = splitValue(lineValue); + switch (values.length) { + case 1: { + const [val1] = values; + if (val1 === value) { + return lineValue; + } + switch (position) { + case "top": { + return [value, val1, val1].join(" "); + } + case "right": { + return [val1, value, val1, val1].join(" "); + } + case "bottom": { + return [val1, val1, value].join(" "); + } + case "left": { + return [val1, val1, val1, value].join(" "); + } + default: + } + break; + } + case 2: { + const [val1, val2] = values; + switch (position) { + case "top": { + if (val1 === value) { + return lineValue; + } + return [value, val2, val1].join(" "); + } + case "right": { + if (val2 === value) { + return lineValue; + } + return [val1, value, val1, val2].join(" "); + } + case "bottom": { + if (val1 === value) { + return lineValue; + } + return [val1, val2, value].join(" "); + } + case "left": { + if (val2 === value) { + return lineValue; + } + return [val1, val2, val1, value].join(" "); + } + default: + } + break; + } + case 3: { + const [val1, val2, val3] = values; + switch (position) { + case "top": { + if (val1 === value) { + return lineValue; + } else if (val3 === value) { + return [value, val2].join(" "); + } + return [value, val2, val3].join(" "); + } + case "right": { + if (val2 === value) { + return lineValue; + } + return [val1, value, val3, val2].join(" "); + } + case "bottom": { + if (val3 === value) { + return lineValue; + } else if (val1 === value) { + return [val1, val2].join(" "); + } + return [val1, val2, value].join(" "); + } + case "left": { + if (val2 === value) { + return lineValue; + } + return [val1, val2, val3, value].join(" "); + } + default: + } + break; + } + case 4: { + const [val1, val2, val3, val4] = values; + switch (position) { + case "top": { + if (val1 === value) { + return lineValue; + } + return [value, val2, val3, val4].join(" "); + } + case "right": { + if (val2 === value) { + return lineValue; + } else if (val4 === value) { + return [val1, value, val3].join(" "); + } + return [val1, value, val3, val4].join(" "); + } + case "bottom": { + if (val3 === value) { + return lineValue; + } + return [val1, val2, value, val4].join(" "); + } + case "left": { + if (val4 === value) { + return lineValue; + } else if (val2 === value) { + return [val1, val2, val3].join(" "); + } + return [val1, val2, val3, value].join(" "); + } + default: + } + break; + } + default: + } +}; + +module.exports.prepareBorderProperties = function prepareBorderProperties( + property, + value, + properties = new Map(), + opt = {} +) { + if (typeof property !== "string" || value === null) { + return; + } + const { globalObject } = opt; + const { name, positions, lines } = borderElements; + const [prop1, prop2, prop3] = property.split("-"); + if (prop1 !== name) { + return; + } else if (positions.includes(prop2)) { + if (prop3) { + if (!lines.includes(prop3)) { + return; + } + } + } else if (lines.includes(prop2)) { + if (prop3) { + return; + } + } + const borderItems = new Map(); + const nameProperty = prop1; + const imageProperty = `${prop1}-image`; + // Empty string, global keywords, var(), value of longhands. + if (typeof value === "string") { + // longhand properties + if (prop3) { + const nameItem = getPropertyItem(nameProperty, properties); + const imageItem = getPropertyItem(imageProperty, properties); + const lineProperty = `${prop1}-${prop3}`; + const lineItem = getPropertyItem(lineProperty, properties); + const positionProperty = `${prop1}-${prop2}`; + const positionItem = getPropertyItem(positionProperty, properties); + const longhandProperty = `${prop1}-${prop2}-${prop3}`; + const longhandItem = getPropertyItem(longhandProperty, properties); + longhandItem.value = value; + const propertyValue = hasVarFunc(value) ? "" : value; + if (propertyValue === "") { + nameItem.value = ""; + lineItem.value = ""; + positionItem.value = ""; + } else if (isGlobalKeyword(propertyValue)) { + if (nameItem.value !== propertyValue) { + nameItem.value = ""; + } + if (lineItem.value !== propertyValue) { + lineItem.value = ""; + } + if (positionItem.value !== propertyValue) { + positionItem.value = ""; + } + } else { + if ( + nameItem.value && + !matchesShorthandValue(lineProperty, propertyValue, nameItem.value, { + globalObject + }) + ) { + nameItem.value = ""; + } + if (lineItem.value) { + lineItem.value = replaceLineValue(propertyValue, lineItem.value, prop2); + } + if ( + positionItem.value && + !matchesShorthandValue(lineProperty, propertyValue, positionItem.value, { + globalObject + }) + ) { + positionItem.value = ""; + } + } + borderItems.set(nameProperty, nameItem); + borderItems.set(imageProperty, imageItem); + borderItems.set(lineProperty, lineItem); + borderItems.set(positionProperty, positionItem); + borderItems.set(longhandProperty, longhandItem); + // border-top, border-right, border-bottom, border-left shorthands + } else if (prop2 && positions.includes(prop2)) { + const nameItem = getPropertyItem(nameProperty, properties); + const imageItem = getPropertyItem(imageProperty, properties); + const lineWidthProperty = `${prop1}-width`; + const lineWidthItem = getPropertyItem(lineWidthProperty, properties); + const lineStyleProperty = `${prop1}-style`; + const lineStyleItem = getPropertyItem(lineStyleProperty, properties); + const lineColorProperty = `${prop1}-color`; + const lineColorItem = getPropertyItem(lineColorProperty, properties); + const positionProperty = `${prop1}-${prop2}`; + const positionItem = getPropertyItem(positionProperty, properties); + positionItem.value = value; + const propertyValue = hasVarFunc(value) ? "" : value; + if (propertyValue === "") { + nameItem.value = ""; + lineWidthItem.value = ""; + lineStyleItem.value = ""; + lineColorItem.value = ""; + } else if (isGlobalKeyword(propertyValue)) { + if (nameItem.value !== propertyValue) { + nameItem.value = ""; + } + if (lineWidthItem.value !== propertyValue) { + lineWidthItem.value = ""; + } + if (lineStyleItem.value !== propertyValue) { + lineStyleItem.value = ""; + } + if (lineColorItem.value !== propertyValue) { + lineColorItem.value = ""; + } + } else { + if ( + nameItem.value && + !matchesShorthandValue(property, propertyValue, nameItem.value, { + globalObject + }) + ) { + nameItem.value = ""; + } + if (lineWidthItem.value && isValidPropertyValue(lineWidthProperty, propertyValue)) { + lineWidthItem.value = propertyValue; + } + if (lineStyleItem.value && isValidPropertyValue(lineStyleProperty, propertyValue)) { + lineStyleItem.value = propertyValue; + } + if (lineColorItem.value && isValidPropertyValue(lineColorProperty, propertyValue)) { + lineColorItem.value = propertyValue; + } + } + for (const line of lines) { + const longhandProperty = `${prop1}-${prop2}-${line}`; + const longhandItem = getPropertyItem(longhandProperty, properties); + longhandItem.value = propertyValue; + borderItems.set(longhandProperty, longhandItem); + } + borderItems.set(nameProperty, nameItem); + borderItems.set(imageProperty, imageItem); + borderItems.set(lineWidthProperty, lineWidthItem); + borderItems.set(lineStyleProperty, lineStyleItem); + borderItems.set(lineColorProperty, lineColorItem); + borderItems.set(positionProperty, positionItem); + // border-width, border-style, border-color + } else if (prop2 && lines.includes(prop2)) { + const nameItem = getPropertyItem(nameProperty, properties); + const imageItem = getPropertyItem(imageProperty, properties); + const lineProperty = `${prop1}-${prop2}`; + const lineItem = getPropertyItem(lineProperty, properties); + lineItem.value = value; + const propertyValue = hasVarFunc(value) ? "" : value; + if (propertyValue === "") { + nameItem.value = ""; + } else if (isGlobalKeyword(propertyValue)) { + if (nameItem.value !== propertyValue) { + nameItem.value = ""; + } + } + for (const position of positions) { + const positionProperty = `${prop1}-${position}`; + const positionItem = getPropertyItem(positionProperty, properties); + const longhandProperty = `${prop1}-${position}-${prop2}`; + const longhandItem = getPropertyItem(longhandProperty, properties); + if (propertyValue) { + positionItem.value = replaceShorthandValue(propertyValue, positionItem.value, { + globalObject + }); + } else { + positionItem.value = ""; + } + longhandItem.value = propertyValue; + borderItems.set(positionProperty, positionItem); + borderItems.set(longhandProperty, longhandItem); + } + borderItems.set(nameProperty, nameItem); + borderItems.set(imageProperty, imageItem); + borderItems.set(lineProperty, lineItem); + // border shorthand + } else { + const nameItem = { + property, + value, + priority: null + }; + const imageItem = getPropertyItem(imageProperty, properties); + const propertyValue = hasVarFunc(value) ? "" : value; + imageItem.value = propertyValue ? "none" : ""; + for (const line of lines) { + const lineProperty = `${prop1}-${line}`; + const lineItem = getPropertyItem(lineProperty, properties); + lineItem.value = propertyValue; + borderItems.set(lineProperty, lineItem); + } + for (const position of positions) { + const positionProperty = `${prop1}-${position}`; + const positionItem = getPropertyItem(positionProperty, properties); + positionItem.value = propertyValue; + borderItems.set(positionProperty, positionItem); + for (const line of lines) { + const longhandProperty = `${positionProperty}-${line}`; + const longhandItem = getPropertyItem(longhandProperty, properties); + longhandItem.value = propertyValue; + borderItems.set(longhandProperty, longhandItem); + } + } + borderItems.set(property, nameItem); + borderItems.set(imageProperty, imageItem); + } + // Values of border-width, border-style, border-color + } else if (Array.isArray(value)) { + if (!value.length || !lines.includes(prop2)) { + return; + } + const nameItem = getPropertyItem(nameProperty, properties); + const imageItem = getPropertyItem(imageProperty, properties); + const lineProperty = `${prop1}-${prop2}`; + const lineItem = getPropertyItem(lineProperty, properties); + if (value.length === 1) { + const [propertyValue] = value; + if (nameItem.value && propertyValue) { + nameItem.value = replaceShorthandValue(propertyValue, nameItem.value, { + globalObject + }); + } + } else { + nameItem.value = ""; + } + lineItem.value = value.join(" "); + const positionValues = {}; + switch (value.length) { + case 1: { + const [val1] = value; + positionValues.top = val1; + positionValues.right = val1; + positionValues.bottom = val1; + positionValues.left = val1; + break; + } + case 2: { + const [val1, val2] = value; + positionValues.top = val1; + positionValues.right = val2; + positionValues.bottom = val1; + positionValues.left = val2; + break; + } + case 3: { + const [val1, val2, val3] = value; + positionValues.top = val1; + positionValues.right = val2; + positionValues.bottom = val3; + positionValues.left = val2; + break; + } + case 4: { + const [val1, val2, val3, val4] = value; + positionValues.top = val1; + positionValues.right = val2; + positionValues.bottom = val3; + positionValues.left = val4; + break; + } + default: { + return; + } + } + for (const position of positions) { + const positionProperty = `${prop1}-${position}`; + const positionItem = getPropertyItem(positionProperty, properties); + if (positionItem.value && positionValues[position]) { + positionItem.value = replaceShorthandValue(positionValues[position], positionItem.value, { + globalObject + }); + } + const longhandProperty = `${positionProperty}-${prop2}`; + const longhandItem = getPropertyItem(longhandProperty, properties); + longhandItem.value = positionValues[position]; + borderItems.set(positionProperty, positionItem); + borderItems.set(longhandProperty, longhandItem); + } + borderItems.set(nameProperty, nameItem); + borderItems.set(imageProperty, imageItem); + borderItems.set(lineProperty, lineItem); + // Values of border, border-top, border-right, border-bottom, border-top. + } else if (value && typeof value === "object") { + // position shorthands + if (prop2) { + if (!positions.includes(prop2)) { + return; + } + const nameItem = getPropertyItem(nameProperty, properties); + const imageItem = getPropertyItem(imageProperty, properties); + const lineWidthProperty = `${prop1}-width`; + const lineWidthItem = getPropertyItem(lineWidthProperty, properties); + const lineStyleProperty = `${prop1}-style`; + const lineStyleItem = getPropertyItem(lineStyleProperty, properties); + const lineColorProperty = `${prop1}-color`; + const lineColorItem = getPropertyItem(lineColorProperty, properties); + const positionProperty = `${prop1}-${prop2}`; + const positionItem = getPropertyItem(positionProperty, properties); + if (nameItem.value) { + for (const lineValue of Object.values(value)) { + if ( + !matchesShorthandValue(property, lineValue, nameItem.value, { + globalObject + }) + ) { + nameItem.value = ""; + break; + } + } + } + positionItem.value = Object.values(value).join(" "); + for (const line of lines) { + const longhandProperty = `${prop1}-${prop2}-${line}`; + const longhandItem = getPropertyItem(longhandProperty, properties); + if (Object.hasOwn(value, longhandProperty)) { + const itemValue = value[longhandProperty]; + if (line === "width") { + if (lineWidthItem.value) { + lineWidthItem.value = replaceLineValue(itemValue, lineWidthItem.value, prop2); + } + } else if (line === "style") { + if (lineStyleItem.value) { + lineStyleItem.value = replaceLineValue(itemValue, lineStyleItem.value, prop2); + } + } else if (line === "color") { + if (lineColorItem.value) { + lineColorItem.value = replaceLineValue(itemValue, lineColorItem.value, prop2); + } + } + longhandItem.value = itemValue; + } else { + const itemValue = border.initialValues.get(`${prop1}-${line}`); + if (line === "width") { + if (lineWidthItem.value) { + lineWidthItem.value = replaceLineValue(itemValue, lineWidthItem.value, prop2); + } + } else if (line === "style") { + if (lineStyleItem.value) { + lineStyleItem.value = replaceLineValue(itemValue, lineStyleItem.value, prop2); + } + } else if (line === "color") { + if (lineColorItem.value) { + lineColorItem.value = replaceLineValue(itemValue, lineColorItem.value, prop2); + } + } + longhandItem.value = itemValue; + } + borderItems.set(longhandProperty, longhandItem); + } + borderItems.set(nameProperty, nameItem); + borderItems.set(imageProperty, imageItem); + borderItems.set(lineWidthProperty, lineWidthItem); + borderItems.set(lineStyleProperty, lineStyleItem); + borderItems.set(lineColorProperty, lineColorItem); + borderItems.set(positionProperty, positionItem); + // border shorthand + } else { + const nameItem = getPropertyItem(prop1, properties); + const imageItem = getPropertyItem(imageProperty, properties); + const lineWidthProperty = `${prop1}-width`; + const lineWidthItem = getPropertyItem(lineWidthProperty, properties); + const lineStyleProperty = `${prop1}-style`; + const lineStyleItem = getPropertyItem(lineStyleProperty, properties); + const lineColorProperty = `${prop1}-color`; + const lineColorItem = getPropertyItem(lineColorProperty, properties); + const propertyValue = Object.values(value).join(" "); + nameItem.value = propertyValue; + imageItem.value = propertyValue ? "none" : ""; + if (Object.hasOwn(value, lineWidthProperty)) { + lineWidthItem.value = value[lineWidthProperty]; + } else { + lineWidthItem.value = border.initialValues.get(lineWidthProperty); + } + if (Object.hasOwn(value, lineStyleProperty)) { + lineStyleItem.value = value[lineStyleProperty]; + } else { + lineStyleItem.value = border.initialValues.get(lineStyleProperty); + } + if (Object.hasOwn(value, lineColorProperty)) { + lineColorItem.value = value[lineColorProperty]; + } else { + lineColorItem.value = border.initialValues.get(lineColorProperty); + } + for (const position of positions) { + const positionProperty = `${prop1}-${position}`; + const positionItem = getPropertyItem(positionProperty, properties); + positionItem.value = propertyValue; + for (const line of lines) { + const longhandProperty = `${positionProperty}-${line}`; + const longhandItem = getPropertyItem(longhandProperty, properties); + const lineProperty = `${prop1}-${line}`; + if (Object.hasOwn(value, lineProperty)) { + longhandItem.value = value[lineProperty]; + } else { + longhandItem.value = border.initialValues.get(lineProperty); + } + borderItems.set(longhandProperty, longhandItem); + } + borderItems.set(positionProperty, positionItem); + } + borderItems.set(property, nameItem); + borderItems.set(imageProperty, imageItem); + borderItems.set(lineWidthProperty, lineWidthItem); + borderItems.set(lineStyleProperty, lineStyleItem); + borderItems.set(lineColorProperty, lineColorItem); + } + } else { + return; + } + if (!borderItems.has(name)) { + return; + } + const borderProperties = new Map([[`${name}`, borderItems.get(name)]]); + for (const line of lines) { + const lineProperty = `${name}-${line}`; + const lineItem = borderItems.get(lineProperty) ?? + properties.get(lineProperty) ?? { + property: lineProperty, + value: "", + priority: null + }; + borderProperties.set(lineProperty, lineItem); + } + for (const position of positions) { + const positionProperty = `${name}-${position}`; + const positionItem = borderItems.get(positionProperty) ?? + properties.get(positionProperty) ?? { + property: positionProperty, + value: "", + priority: null + }; + borderProperties.set(positionProperty, positionItem); + for (const line of lines) { + const longhandProperty = `${name}-${position}-${line}`; + const longhandItem = borderItems.get(longhandProperty) ?? + properties.get(longhandProperty) ?? { + property: longhandProperty, + value: "", + priority: null + }; + borderProperties.set(longhandProperty, longhandItem); + } + } + const borderImageItem = borderItems.get(imageProperty) ?? { + property: imageProperty, + value: "", + priority: null + }; + borderProperties.set(imageProperty, borderImageItem); + return borderProperties; +}; + +module.exports.normalizeBorderProperties = function (properties) { + const { name, positions, lines } = borderElements; + if (properties.has(name)) { + for (const line of lines) { + properties.delete(`${name}-${line}`); + } + for (const position of positions) { + properties.delete(`${name}-${position}`); + for (const line of lines) { + properties.delete(`${name}-${position}-${line}`); + } + } + properties.delete(`${name}-image`); + } + for (const line of lines) { + const lineProperty = `${name}-${line}`; + if (properties.has(lineProperty)) { + const positionValues = []; + for (const position of positions) { + const positionProperty = `${name}-${position}`; + if (properties.has(positionProperty)) { + const positionItem = properties.get(positionProperty); + positionValues.push(positionItem.value); + } + properties.delete(`${positionProperty}-${line}`); + } + if (positionValues.length === 4) { + const lineItem = properties.get(lineProperty); + if (positionValues.every((positionValue) => positionValue === lineItem.value)) { + for (const position of positions) { + properties.delete(`${name}-${position}`); + } + } + } + } + } + const initialValuePositionProperties = []; + const omitPositionProperties = []; + for (const position of positions) { + const positionProperty = `${name}-${position}`; + if (properties.has(positionProperty)) { + const positionValue = properties.get(positionProperty); + if (positionValue.value === firstInitialValue) { + initialValuePositionProperties.push(positionProperty); + } + let omitPositionProperty = true; + for (const line of lines) { + const lineProperty = `${name}-${line}`; + if (!properties.has(lineProperty)) { + omitPositionProperty = false; + } + properties.delete(`${name}-${position}-${line}`); + } + if (omitPositionProperty) { + omitPositionProperties.push(positionProperty); + } + } else { + const lineValueItems = new Map(); + for (const line of lines) { + const longhandProperty = `${name}-${position}-${line}`; + if (properties.has(longhandProperty)) { + const longhandItem = properties.get(longhandProperty); + lineValueItems.set(`${name}-${line}`, longhandItem.value); + } + } + if (lineValueItems.size === 3) { + const obj = { + [firstInitialKey]: firstInitialValue + }; + for (const line of lines) { + const lineProperty = `${name}-${line}`; + const initialValue = border.initialValues.get(lineProperty); + const lineValue = lineValueItems.get(lineProperty); + if (lineValue !== initialValue) { + obj[lineProperty] = lineValue; + if (obj[firstInitialKey] && obj[firstInitialKey] === firstInitialValue) { + delete obj[firstInitialKey]; + } + } + properties.delete(`${name}-${position}-${line}`); + } + properties.set(positionProperty, { + property: positionProperty, + value: Object.values(obj).join(" "), + priority: null + }); + } + } + } + if (initialValuePositionProperties.length === 4) { + for (const positionProperty of initialValuePositionProperties) { + properties.delete(positionProperty); + } + } + if (omitPositionProperties.length === 4) { + for (const positionProperty of omitPositionProperties) { + properties.delete(positionProperty); + } + } + return properties; +}; diff --git a/test/CSSStyleDeclaration.test.js b/test/CSSStyleDeclaration.test.js index 00694410..ccb1e02a 100644 --- a/test/CSSStyleDeclaration.test.js +++ b/test/CSSStyleDeclaration.test.js @@ -267,7 +267,7 @@ describe("CSSStyleDeclaration", () => { it("implicit properties", () => { const style = new CSSStyleDeclaration(); style.borderWidth = 0; - assert.strictEqual(style.length, 1); + assert.strictEqual(style.border, ""); assert.strictEqual(style.borderWidth, "0px"); assert.strictEqual(style.borderTopWidth, "0px"); assert.strictEqual(style.borderBottomWidth, "0px"); @@ -383,58 +383,294 @@ describe("CSSStyleDeclaration", () => { it("setting a shorthand property, whose shorthands are implicit properties, to an empty string should clear all dependent properties", () => { const style = new CSSStyleDeclaration(); - style.borderTopWidth = "1px"; - assert.strictEqual(style.cssText, "border-top-width: 1px;"); + style.borderTopWidth = "2px"; + assert.strictEqual(style.cssText, "border-top-width: 2px;"); style.border = ""; assert.strictEqual(style.cssText, ""); - style.borderTop = "1px solid black"; - assert.strictEqual(style.cssText, "border-top: 1px solid black;"); + style.borderTop = "2px solid black"; + assert.strictEqual(style.cssText, "border-top: 2px solid black;"); style.border = ""; assert.strictEqual(style.cssText, ""); }); - it('setting border values to "none" should change dependent values', () => { + it("set border as none", () => { + const style = new CSSStyleDeclaration(); + style.border = "none"; + assert.strictEqual(style.border, "medium", "border"); + assert.strictEqual(style.borderWidth, "medium", "border-width"); + assert.strictEqual(style.borderStyle, "none", "border-style"); + assert.strictEqual(style.borderTop, "medium", "border-top"); + assert.strictEqual(style.borderTopWidth, "medium", "border-top-width"); + assert.strictEqual(style.borderTopStyle, "none", "border-top-style"); + assert.strictEqual(style.borderImage, "none", "border-image"); + assert.strictEqual(style.cssText, "border: medium;", "cssText"); + }); + + it("set border as none", () => { + const style = new CSSStyleDeclaration(); + style.border = "none"; + assert.strictEqual(style.border, "medium", "border"); + assert.strictEqual(style.borderWidth, "medium", "border-width"); + assert.strictEqual(style.borderStyle, "none", "border-style"); + assert.strictEqual(style.borderTop, "medium", "border-top"); + assert.strictEqual(style.borderTopWidth, "medium", "border-top-width"); + assert.strictEqual(style.borderTopStyle, "none", "border-top-style"); + assert.strictEqual(style.borderImage, "none", "border-image"); + assert.strictEqual(style.cssText, "border: medium;", "cssText"); + }); + + it("set border-style as none", () => { + const style = new CSSStyleDeclaration(); + style.borderStyle = "none"; + assert.strictEqual(style.border, "", "border"); + assert.strictEqual(style.borderWidth, "", "border-width"); + assert.strictEqual(style.borderStyle, "none", "border-style"); + assert.strictEqual(style.borderTop, "", "border-top"); + assert.strictEqual(style.borderTopWidth, "", "border-top-width"); + assert.strictEqual(style.borderTopStyle, "none", "border-top-style"); + assert.strictEqual(style.borderImage, "", "border-image"); + assert.strictEqual(style.cssText, "border-style: none;", "cssText"); + }); + + it("set border-top as none", () => { + const style = new CSSStyleDeclaration(); + style.borderTop = "none"; + assert.strictEqual(style.border, "", "border"); + assert.strictEqual(style.borderWidth, "", "border-width"); + assert.strictEqual(style.borderStyle, "", "border-style"); + assert.strictEqual(style.borderTop, "medium", "border-top"); + assert.strictEqual(style.borderTopStyle, "none", "border-top-style"); + assert.strictEqual(style.borderTopWidth, "medium", "border-top-width"); + assert.strictEqual(style.borderImage, "", "border-image"); + assert.strictEqual(style.cssText, "border-top: medium;", "cssText"); + }); + + it("set border as 1px and change border-style to none", () => { + const style = new CSSStyleDeclaration(); + style.border = "1px"; + style.borderStyle = "none"; + assert.strictEqual(style.border, "1px", "border"); + assert.strictEqual(style.borderWidth, "1px", "border-width"); + assert.strictEqual(style.borderStyle, "none", "border-style"); + assert.strictEqual(style.borderTop, "1px", "border-top"); + assert.strictEqual(style.borderTopWidth, "1px", "border-top-width"); + assert.strictEqual(style.borderTopStyle, "none", "border-top-style"); + assert.strictEqual(style.borderImage, "none", "border-image"); + assert.strictEqual(style.cssText, "border: 1px;", "cssText"); + }); + + it("set border as 1px and change border-style to none", () => { + const style = new CSSStyleDeclaration(); + style.border = "1px"; + style.borderStyle = "none"; + assert.strictEqual(style.border, "1px", "border"); + assert.strictEqual(style.borderWidth, "1px", "border-width"); + assert.strictEqual(style.borderStyle, "none", "border-style"); + assert.strictEqual(style.borderTop, "1px", "border-top"); + assert.strictEqual(style.borderTopWidth, "1px", "border-top-width"); + assert.strictEqual(style.borderTopStyle, "none", "border-top-style"); + assert.strictEqual(style.borderImage, "none", "border-image"); + assert.strictEqual(style.cssText, "border: 1px;", "cssText"); + }); + + it("set border as 1px and change border-top to none", () => { + const style = new CSSStyleDeclaration(); + style.border = "1px"; + style.borderTop = "none"; + assert.strictEqual(style.border, "", "border"); + assert.strictEqual(style.borderWidth, "medium 1px 1px", "border-width"); + assert.strictEqual(style.borderStyle, "none", "border-style"); + assert.strictEqual(style.borderTop, "medium", "border-top"); + assert.strictEqual(style.borderTopWidth, "medium", "border-top-width"); + assert.strictEqual(style.borderTopStyle, "none", "border-top-style"); + assert.strictEqual(style.borderImage, "none", "border-image"); + assert.strictEqual( + style.cssText, + "border-width: medium 1px 1px; border-style: none; border-color: currentcolor; border-image: none;", + "cssText" + ); + }); + + it("set border as 1px solid and change border-top to none", () => { + const style = new CSSStyleDeclaration(); + style.border = "1px solid"; + style.borderTop = "none"; + assert.strictEqual(style.border, "", "border"); + assert.strictEqual(style.borderWidth, "medium 1px 1px", "border-width"); + assert.strictEqual(style.borderStyle, "none solid solid", "border-style"); + assert.strictEqual(style.borderTop, "medium", "border-top"); + assert.strictEqual(style.borderTopWidth, "medium", "border-top-width"); + assert.strictEqual(style.borderTopStyle, "none", "border-top-style"); + assert.strictEqual(style.borderImage, "none", "border-image"); + assert.strictEqual( + style.cssText, + "border-width: medium 1px 1px; border-style: none solid solid; border-color: currentcolor; border-image: none;", + "cssText" + ); + }); + + it("set border as none and change border-style to null", () => { + const style = new CSSStyleDeclaration(); + style.border = "none"; + style.borderStyle = null; + assert.strictEqual(style.border, "", "border"); + assert.strictEqual(style.borderWidth, "medium", "border-width"); + assert.strictEqual(style.borderStyle, "", "border-style"); + assert.strictEqual(style.borderTop, "", "border-top"); + assert.strictEqual(style.borderTopWidth, "medium", "border-top-width"); + assert.strictEqual(style.borderTopStyle, "", "border-top-style"); + assert.strictEqual(style.borderImage, "none", "border-image"); + assert.strictEqual( + style.cssText, + "border-width: medium; border-color: currentcolor; border-image: none;", + "cssText" + ); + }); + + it("set border as solid and change border-top to none", () => { + const style = new CSSStyleDeclaration(); + style.border = "solid"; + style.borderTop = "none"; + assert.strictEqual(style.border, "", "border"); + assert.strictEqual(style.borderWidth, "medium", "border-width"); + assert.strictEqual(style.borderStyle, "none solid solid", "border-style"); + assert.strictEqual(style.borderTop, "medium", "border-top"); + assert.strictEqual(style.borderTopWidth, "medium", "border-top-width"); + assert.strictEqual(style.borderTopStyle, "none", "border-top-style"); + assert.strictEqual(style.borderImage, "none", "border-image"); + assert.strictEqual( + style.cssText, + "border-width: medium; border-style: none solid solid; border-color: currentcolor; border-image: none;", + "cssText" + ); + }); + + it("set border as solid and change border-style to none", () => { + const style = new CSSStyleDeclaration(); + style.border = "solid"; + style.borderStyle = "none"; + assert.strictEqual(style.border, "medium", "border"); + assert.strictEqual(style.borderWidth, "medium", "border-width"); + assert.strictEqual(style.borderStyle, "none", "border-style"); + assert.strictEqual(style.borderTop, "medium", "border-top"); + assert.strictEqual(style.borderTopWidth, "medium", "border-top-width"); + assert.strictEqual(style.borderTopStyle, "none", "border-top-style"); + assert.strictEqual(style.borderImage, "none", "border-image"); + assert.strictEqual(style.cssText, "border: medium;", "cssText"); + }); + + it("set border-style as solid and change border-top to null", () => { + const style = new CSSStyleDeclaration(); + style.borderStyle = "solid"; + style.borderTop = null; + assert.strictEqual( + style.cssText, + "border-right-style: solid; border-bottom-style: solid; border-left-style: solid;", + "cssText" + ); + assert.strictEqual(style.border, "", "border"); + assert.strictEqual(style.borderWidth, "", "border-width"); + assert.strictEqual(style.borderStyle, "", "border-style"); + assert.strictEqual(style.borderTop, "", "border-top"); + assert.strictEqual(style.borderTopWidth, "", "border-top-width"); + assert.strictEqual(style.borderTopStyle, "", "border-top-style"); + assert.strictEqual(style.borderImage, "", "borrder-image"); + }); + + it("setting border values to none should change dependent values", () => { const style = new CSSStyleDeclaration(); style.borderTopWidth = "1px"; assert.strictEqual(style.cssText, "border-top-width: 1px;"); style.border = "none"; - assert.strictEqual(style.cssText, "border: none;"); + assert.strictEqual(style.border, "medium"); + assert.strictEqual(style.borderTop, "medium"); assert.strictEqual(style.borderTopStyle, "none"); assert.strictEqual(style.borderTopWidth, "medium"); + assert.strictEqual(style.cssText, "border: medium;"); style.border = null; style.borderImage = null; style.borderTopWidth = "1px"; assert.strictEqual(style.cssText, "border-top-width: 1px;"); style.borderStyle = "none"; - assert.strictEqual(style.cssText, "border-top-width: 1px; border-style: none;"); assert.strictEqual(style.borderTopStyle, "none"); assert.strictEqual(style.borderTopWidth, "1px"); + assert.strictEqual(style.cssText, "border-top-width: 1px; border-style: none;"); style.border = null; style.borderImage = null; style.borderTopWidth = "1px"; assert.strictEqual(style.cssText, "border-top-width: 1px;"); style.borderTop = "none"; - assert.strictEqual(style.cssText, "border-top: none;"); assert.strictEqual(style.borderTopStyle, "none"); assert.strictEqual(style.borderTopWidth, "medium"); + assert.strictEqual(style.cssText, "border-top: medium;"); style.border = null; style.borderImage = null; style.borderTopWidth = "1px"; assert.strictEqual(style.cssText, "border-top-width: 1px;"); style.borderTopStyle = "none"; - assert.strictEqual(style.cssText, "border-top-width: 1px; border-top-style: none;"); assert.strictEqual(style.borderTopStyle, "none"); assert.strictEqual(style.borderTopWidth, "1px"); - }); + assert.strictEqual(style.cssText, "border-top-width: 1px; border-top-style: none;"); - it("setting border to 1px should be okay", () => { - const style = new CSSStyleDeclaration(); + style.border = null; + style.borderImage = null; style.border = "1px"; assert.strictEqual(style.cssText, "border: 1px;"); assert.strictEqual(style.border, "1px"); + assert.strictEqual(style.borderTopStyle, "none"); + assert.strictEqual(style.borderTopWidth, "1px"); + style.borderTop = "none"; + assert.strictEqual( + style.cssText, + "border-width: medium 1px 1px; border-style: none; border-color: currentcolor; border-image: none;" + ); + }); + + it("setting border to green", () => { + const style = new CSSStyleDeclaration(); + style.border = "green"; + assert.strictEqual(style.cssText, "border: green;"); + assert.strictEqual(style.border, "green"); + }); + + it("setting border to green", () => { + const style = new CSSStyleDeclaration(); + style.border = "green"; + assert.strictEqual(style.cssText, "border: green;"); + assert.strictEqual(style.border, "green"); + }); + + it("setting border to initial should set all properties initial", () => { + const style = new CSSStyleDeclaration(); + style.border = "initial"; + assert.strictEqual(style.cssText, "border: initial;"); + assert.strictEqual(style.border, "initial"); + assert.strictEqual(style.borderWidth, "initial"); + assert.strictEqual(style.borderStyle, "initial"); + assert.strictEqual(style.borderColor, "initial"); + assert.strictEqual(style.borderTop, "initial"); + assert.strictEqual(style.borderTopWidth, "initial"); + assert.strictEqual(style.borderTopStyle, "initial"); + assert.strictEqual(style.borderTopColor, "initial"); + assert.strictEqual(style.borderImage, "none"); + }); + + it("setting borderTop to initial should set top related properties initial", () => { + const style = new CSSStyleDeclaration(); + style.borderTop = "initial"; + assert.strictEqual(style.cssText, "border-top: initial;"); + assert.strictEqual(style.border, ""); + assert.strictEqual(style.borderWidth, ""); + assert.strictEqual(style.borderStyle, ""); + assert.strictEqual(style.borderColor, ""); + assert.strictEqual(style.borderTop, "initial"); + assert.strictEqual(style.borderTopWidth, "initial"); + assert.strictEqual(style.borderTopStyle, "initial"); + assert.strictEqual(style.borderTopColor, "initial"); + assert.strictEqual(style.borderImage, ""); }); it("setting border to 0 should be okay", () => { diff --git a/test/parsers.test.js b/test/parsers.test.js index 94ffc847..2f6505db 100644 --- a/test/parsers.test.js +++ b/test/parsers.test.js @@ -343,7 +343,7 @@ describe("parseLength", () => { assert.strictEqual(output, ""); }); - it("should return empty string", () => { + it("should return undefined", () => { const input = "100"; const output = parsers.parseLength(input); From a798d3ba455aa912bd517846af7177b39f4bca52 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Thu, 21 Aug 2025 07:14:15 +0900 Subject: [PATCH 38/41] Update parsers --- lib/parsers.js | 673 +++++------------ lib/properties/backgroundImage.js | 8 + lib/properties/clip.js | 3 +- test/parsers.test.js | 1112 +++++++++++++++-------------- 4 files changed, 793 insertions(+), 1003 deletions(-) diff --git a/lib/parsers.js b/lib/parsers.js index e4b23746..6ea74fb0 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -140,6 +140,7 @@ exports.isValidPropertyValue = function isValidPropertyValue(prop, val) { } // cssTree.lexer does not support deprecated system colors // @see https://github.com/w3c/webref/issues/1519#issuecomment-3120290261 + // @see https://github.com/w3c/webref/issues/1647 if (SYS_COLOR.includes(asciiLowercase(val))) { if (/^(?:-webkit-)?(?:[a-z][a-z\d]*-)*color$/i.test(prop)) { return true; @@ -158,39 +159,6 @@ exports.isValidPropertyValue = function isValidPropertyValue(prop, val) { return error === null && matched !== null; }; -// Parse CSS function to AST. -exports.parseFunction = function parseFunction(val) { - if (typeof val !== "string") { - val = exports.prepareValue(val); - } - if (val === "") { - return { - name: null, - value: "", - hasVar: false, - raw: "" - }; - } - const obj = exports.parseCSS(val, { context: "value" }, true); - if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { - return; - } - const [{ name, type }] = obj.children; - if (type !== "Function") { - return; - } - const value = val - .replace(new RegExp(`^${name}\\(`), "") - .replace(/\)$/, "") - .trim(); - return { - name, - value, - hasVar: exports.hasVarFunc(val), - raw: val - }; -}; - exports.parseCalc = function parseCalc(val, opt = { format: "specifiedValue" }) { if (typeof val !== "string") { val = exports.prepareValue(val); @@ -226,451 +194,6 @@ exports.parseCalc = function parseCalc(val, opt = { format: "specifiedValue" }) return values.join(" "); }; -exports.parseNumber = function parseNumber(val, opt = {}) { - const { clamp } = opt; - const max = opt.max ?? Number.INFINITY; - const min = opt.min ?? Number.NEGATIVE_INFINITY; - const items = []; - if (Array.isArray(val)) { - items.push(...val); - } else { - if (typeof val !== "string") { - val = exports.prepareValue(val); - } - if (val === "" || exports.hasVarFunc(val)) { - return val; - } else if (exports.hasCalcFunc(val)) { - return exports.parseCalc(val, { - format: "specifiedValue" - }); - } - const obj = exports.parseCSS(val, { context: "value" }, true); - if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { - return; - } - items.push(...obj.children); - } - if (!items.length) { - return; - } - const [item] = items; - const { type, value } = item ?? {}; - if (type !== "Number") { - return; - } - let num = parseFloat(value); - if (clamp) { - if (num > max) { - num = max; - } else if (num < min) { - num = min; - } - } else if (num > max || num < min) { - return; - } - return `${num}`; -}; - -exports.parseLength = function parseLength(val, opt = {}) { - const { clamp } = opt; - const max = opt.max ?? Number.INFINITY; - const min = opt.min ?? Number.NEGATIVE_INFINITY; - const items = []; - if (Array.isArray(val)) { - items.push(...val); - } else { - if (typeof val !== "string") { - val = exports.prepareValue(val); - } - if (val === "" || exports.hasVarFunc(val)) { - return val; - } else if (exports.hasCalcFunc(val)) { - return exports.parseCalc(val, { - format: "specifiedValue" - }); - } - const obj = exports.parseCSS(val, { context: "value" }, true); - if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { - return; - } - items.push(...obj.children); - } - if (!items.length) { - return; - } - const [item] = items; - const { type, value, unit } = item ?? {}; - if (type !== "Dimension" && !(type === "Number" && value === "0")) { - return; - } - let num = parseFloat(value); - if (clamp) { - if (num > max) { - num = max; - } else if (num < min) { - num = min; - } - } else if (num > max || num < min) { - return; - } - if (num === 0 && !unit) { - return `${num}px`; - } else if (unit) { - return `${num}${asciiLowercase(unit)}`; - } -}; - -exports.parsePercent = function parsePercent(val, opt = {}) { - const { clamp } = opt; - const max = opt.max ?? Number.INFINITY; - const min = opt.min ?? Number.NEGATIVE_INFINITY; - const items = []; - if (Array.isArray(val)) { - items.push(...val); - } else { - if (typeof val !== "string") { - val = exports.prepareValue(val); - } - if (val === "" || exports.hasVarFunc(val)) { - return val; - } else if (exports.hasCalcFunc(val)) { - return exports.parseCalc(val, { - format: "specifiedValue" - }); - } - const obj = exports.parseCSS(val, { context: "value" }, true); - if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { - return; - } - items.push(...obj.children); - } - if (!items.length) { - return; - } - const [item] = items; - const { type, value } = item ?? {}; - if (type !== "Percentage" && !(type === "Number" && value === "0")) { - return; - } - let num = parseFloat(value); - if (clamp) { - if (num > max) { - num = max; - } else if (num < min) { - num = min; - } - } else if (num > max || num < min) { - return; - } - if (num === 0) { - return `${num}%`; - } - return `${num}%`; -}; - -// Either a length or a percent. -exports.parseMeasurement = function parseMeasurement(val, opt = {}) { - const { clamp } = opt; - const max = opt.max ?? Number.INFINITY; - const min = opt.min ?? Number.NEGATIVE_INFINITY; - const items = []; - if (Array.isArray(val)) { - items.push(...val); - } else { - if (typeof val !== "string") { - val = exports.prepareValue(val); - } - if (val === "" || exports.hasVarFunc(val)) { - return val; - } else if (exports.hasCalcFunc(val)) { - return exports.parseCalc(val, { - format: "specifiedValue" - }); - } - const obj = exports.parseCSS(val, { context: "value" }, true); - if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { - return; - } - items.push(...obj.children); - } - if (!items.length) { - return; - } - const [item] = items; - const { type, value, unit } = item ?? {}; - if (type !== "Dimension" && type !== "Percentage" && !(type === "Number" && value === "0")) { - return; - } - let num = parseFloat(value); - if (clamp) { - if (num > max) { - num = max; - } else if (num < min) { - num = min; - } - } else if (num > max || num < min) { - return; - } - if (unit) { - return `${num}${asciiLowercase(unit)}`; - } else if (type === "Percentage") { - return `${num}%`; - } else if (num === 0) { - return `${num}px`; - } -}; - -exports.parseAngle = function parseAngle(val) { - const items = []; - if (Array.isArray(val)) { - items.push(...val); - } else { - if (typeof val !== "string") { - val = exports.prepareValue(val); - } - if (val === "" || exports.hasVarFunc(val)) { - return val; - } else if (exports.hasCalcFunc(val)) { - return exports.parseCalc(val, { - format: "specifiedValue" - }); - } - const obj = exports.parseCSS(val, { context: "value" }, true); - if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { - return; - } - items.push(...obj.children); - } - if (!items.length) { - return; - } - const [item] = items; - const { type, value, unit } = item ?? {}; - if (type !== "Dimension" && !(type === "Number" && value === "0")) { - return; - } - const num = parseFloat(value); - if (unit) { - return `${num}${asciiLowercase(unit)}`; - } else if (num === 0) { - return `${num}deg`; - } -}; - -exports.parseUrl = function parseUrl(val) { - const items = []; - if (Array.isArray(val)) { - items.push(...val); - } else { - if (typeof val !== "string") { - val = exports.prepareValue(val); - } - if (val === "") { - return val; - } - let obj; - try { - obj = exports.parseCSS(val, { context: "value" }, true); - } catch { - return; - } - if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { - return; - } - items.push(...obj.children); - } - if (!items.length) { - return; - } - const [item] = items; - const { type, value } = item ?? {}; - if (type !== "Url") { - return; - } - const str = value.replace(/\\\\/g, "\\").replaceAll('"', '\\"'); - return `url("${str}")`; -}; - -exports.parseString = function parseString(val) { - const items = []; - if (Array.isArray(val)) { - items.push(...val); - } else { - if (typeof val !== "string") { - val = exports.prepareValue(val); - } - if (val === "") { - return val; - } - let obj; - try { - obj = exports.parseCSS(val, { context: "value" }, true); - } catch { - return; - } - if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { - return; - } - items.push(...obj.children); - } - if (!items.length) { - return; - } - const [item] = items; - const { type, value } = item ?? {}; - if (type !== "String") { - return; - } - const str = value.replace(/\\\\/g, "\\").replaceAll('"', '\\"'); - return `"${str}"`; -}; - -exports.parseKeyword = function parseKeyword(val, validKeywords = []) { - const items = []; - if (Array.isArray(val)) { - items.push(...val); - } else { - if (typeof val !== "string") { - val = exports.prepareValue(val); - } - if (val === "") { - return val; - } - let obj; - try { - obj = exports.parseCSS(val, { context: "value" }, true); - } catch { - return; - } - if (!obj || !Array.isArray(obj.children) || obj.children.length > 1) { - return; - } - items.push(...obj.children); - } - if (!items.length) { - return; - } - const [item] = items; - const { name, type } = item ?? {}; - if (type !== "Identifier" || name === "undefined") { - return; - } - const value = asciiLowercase(name); - if (validKeywords.includes(value) || GLOBAL_KEY.includes(value)) { - return value; - } -}; - -exports.parseColor = function parseColor(val) { - const items = []; - if (Array.isArray(val)) { - items.push(...val); - } else { - if (typeof val !== "string") { - val = exports.prepareValue(val); - } - if (val === "" || exports.hasVarFunc(val)) { - return val; - } - if (/^[a-z]+$/i.test(val)) { - const v = asciiLowercase(val); - if (SYS_COLOR.includes(v)) { - return v; - } - } - items.push({ - type: "Function", - raw: val - }); - } - if (!items.length) { - return; - } - const [item] = items; - const { name, raw, type, value } = item; - switch (type) { - case "Function": { - const res = resolveColor(raw, { - format: "specifiedValue" - }); - if (res) { - return res; - } - break; - } - case "Hash": { - const res = resolveColor(`#${value}`, { - format: "specifiedValue" - }); - if (res) { - return res; - } - break; - } - case "Identifier": { - if (SYS_COLOR.includes(name)) { - return name; - } - const res = resolveColor(name, { - format: "specifiedValue" - }); - if (res) { - return res; - } - break; - } - default: - } -}; - -exports.parseImage = function parseImage(val) { - const items = []; - if (Array.isArray(val)) { - items.push(...val); - } else { - if (typeof val !== "string") { - val = exports.prepareValue(val); - } - if (val === "" || exports.hasVarFunc(val)) { - return val; - } - const func = exports.parseFunction(val); - const url = exports.parseUrl(val); - if (func) { - func.type = "Function"; - items.push(func); - } else if (url) { - items.push({ - type: "Url", - value: url.replace(/^url\("/, "").replace(/"\)$/, "") - }); - } - } - if (!items.length) { - return; - } - const [item] = items; - const { name, raw, type } = item ?? {}; - switch (type) { - case "Function": { - const res = resolveGradient(raw, { - format: "specifiedValue" - }); - if (res) { - return res; - } - break; - } - case "Identifier": { - return name; - } - case "Url": { - return exports.parseUrl(items); - } - default: - } -}; - exports.parseShorthand = function parseShorthand(val, shorthandFor, opt = {}) { const { globalObject, preserve } = opt; if (typeof val !== "string") { @@ -851,3 +374,197 @@ exports.parsePropertyValue = function parsePropertyValue(prop, val, opt = {}) { } return val; }; + +exports.parseNumber = function parseNumber(val, opt = {}) { + const [item] = val; + const { type, value } = item ?? {}; + if (type !== "Number") { + return; + } + const { clamp } = opt; + const max = opt.max ?? Number.INFINITY; + const min = opt.min ?? Number.NEGATIVE_INFINITY; + let num = parseFloat(value); + if (clamp) { + if (num > max) { + num = max; + } else if (num < min) { + num = min; + } + } else if (num > max || num < min) { + return; + } + return `${num}`; +}; + +exports.parseLength = function parseLength(val, opt = {}) { + const [item] = val; + const { type, value, unit } = item ?? {}; + if (type !== "Dimension" && !(type === "Number" && value === "0")) { + return; + } + const { clamp } = opt; + const max = opt.max ?? Number.INFINITY; + const min = opt.min ?? Number.NEGATIVE_INFINITY; + let num = parseFloat(value); + if (clamp) { + if (num > max) { + num = max; + } else if (num < min) { + num = min; + } + } else if (num > max || num < min) { + return; + } + if (num === 0 && !unit) { + return `${num}px`; + } else if (unit) { + return `${num}${asciiLowercase(unit)}`; + } +}; + +exports.parsePercent = function parsePercent(val, opt = {}) { + const [item] = val; + const { type, value } = item ?? {}; + if (type !== "Percentage" && !(type === "Number" && value === "0")) { + return; + } + const { clamp } = opt; + const max = opt.max ?? Number.INFINITY; + const min = opt.min ?? Number.NEGATIVE_INFINITY; + let num = parseFloat(value); + if (clamp) { + if (num > max) { + num = max; + } else if (num < min) { + num = min; + } + } else if (num > max || num < min) { + return; + } + if (num === 0) { + return `${num}%`; + } + return `${num}%`; +}; + +// Either a length or a percent. +exports.parseMeasurement = function parseMeasurement(val, opt = {}) { + const [item] = val; + const { type, value, unit } = item ?? {}; + if (type !== "Dimension" && type !== "Percentage" && !(type === "Number" && value === "0")) { + return; + } + const { clamp } = opt; + const max = opt.max ?? Number.INFINITY; + const min = opt.min ?? Number.NEGATIVE_INFINITY; + let num = parseFloat(value); + if (clamp) { + if (num > max) { + num = max; + } else if (num < min) { + num = min; + } + } else if (num > max || num < min) { + return; + } + if (unit) { + if (/deg|g?rad|turn/i.test(unit)) { + return; + } + return `${num}${asciiLowercase(unit)}`; + } else if (type === "Percentage") { + return `${num}%`; + } else if (num === 0) { + return `${num}px`; + } +}; + +exports.parseAngle = function parseAngle(val) { + const [item] = val; + const { type, value, unit } = item ?? {}; + if (type !== "Dimension" && !(type === "Number" && value === "0")) { + return; + } + const num = parseFloat(value); + if (unit) { + if (!/^(?:deg|g?rad|turn)$/i.test(unit)) { + return; + } + return `${num}${asciiLowercase(unit)}`; + } else if (num === 0) { + return `${num}deg`; + } +}; + +exports.parseUrl = function parseUrl(val) { + const [item] = val; + const { type, value } = item ?? {}; + if (type !== "Url") { + return; + } + const str = value.replace(/\\\\/g, "\\").replaceAll('"', '\\"'); + return `url("${str}")`; +}; + +exports.parseString = function parseString(val) { + const [item] = val; + const { type, value } = item ?? {}; + if (type !== "String") { + return; + } + const str = value.replace(/\\\\/g, "\\").replaceAll('"', '\\"'); + return `"${str}"`; +}; + +exports.parseColor = function parseColor(val) { + const [item] = val; + const { name, type, value } = item ?? {}; + switch (type) { + case "Function": { + const res = resolveColor(`${name}(${value})`, { + format: "specifiedValue" + }); + if (res) { + return res; + } + break; + } + case "Hash": { + const res = resolveColor(`#${value}`, { + format: "specifiedValue" + }); + if (res) { + return res; + } + break; + } + case "Identifier": { + if (SYS_COLOR.includes(name)) { + return name; + } + const res = resolveColor(name, { + format: "specifiedValue" + }); + if (res) { + return res; + } + break; + } + default: + } +}; + +exports.parseImage = function parseImage(val) { + const [item] = val; + const { name, type, value } = item ?? {}; + if (type !== "Function") { + return; + } + const res = resolveGradient(`${name}(${value})`, { + format: "specifiedValue" + }); + if (res) { + return res; + } +}; diff --git a/lib/properties/backgroundImage.js b/lib/properties/backgroundImage.js index a023d1f4..add73ef5 100644 --- a/lib/properties/backgroundImage.js +++ b/lib/properties/backgroundImage.js @@ -22,6 +22,14 @@ module.exports.parse = function parse(v, opt = {}) { parsedValues.push(name); break; } + case "Url": { + const parsedValue = parsers.parseUrl(value); + if (!parsedValue) { + return; + } + parsedValues.push(parsedValue); + break; + } default: { const parsedValue = parsers.parseImage(value); if (!parsedValue) { diff --git a/lib/properties/clip.js b/lib/properties/clip.js index 75f09853..ba927200 100644 --- a/lib/properties/clip.js +++ b/lib/properties/clip.js @@ -22,7 +22,8 @@ module.exports.parse = function parse(v, opt = {}) { }); const parsedValues = []; for (const item of values) { - const val = parsers.parseMeasurement(item); + const parsedValue = parsers.parseCSS(item, { context: "value" }, true); + const val = parsers.parseMeasurement(parsedValue.children); if (val) { parsedValues.push(val); } else { diff --git a/test/parsers.test.js b/test/parsers.test.js index 2f6505db..1ebaa61a 100644 --- a/test/parsers.test.js +++ b/test/parsers.test.js @@ -1,5 +1,4 @@ "use strict"; -/* eslint-disable no-useless-escape */ const { describe, it } = require("node:test"); const assert = require("node:assert/strict"); @@ -247,53 +246,99 @@ describe("parseCalc", () => { assert.strictEqual(output, "calc(100% - 2em)"); }); + + it("should return calculated value", () => { + const input = "calc(2em / 3)"; + const output = parsers.parseCalc(input); + + assert.strictEqual(output, "calc(0.666667em)"); + }); + + it("should return serialized value", () => { + const input = "calc(10px + 20%)"; + const output = parsers.parseCalc(input); + + assert.strictEqual(output, "calc(20% + 10px)"); + }); + + it("should return serialized value", () => { + const input = "calc(100vh + 10px)"; + const output = parsers.parseCalc(input); + + assert.strictEqual(output, "calc(10px + 100vh)"); + }); }); describe("parseNumber", () => { - it("should return empty string", () => { + it("should return undefind", () => { const input = ""; const output = parsers.parseNumber(input); - assert.strictEqual(output, ""); + assert.strictEqual(output, undefined); + }); + + it("should return undefind", () => { + const input = []; + const output = parsers.parseNumber(input); + + assert.strictEqual(output, undefined); }); it("should return undefined", () => { - const input = "foo"; + const input = [ + { + type: "Percent", + value: "100" + } + ]; const output = parsers.parseNumber(input); assert.strictEqual(output, undefined); }); it('should return "1"', () => { - const input = "1"; + const input = [ + { + type: "Number", + value: "1" + } + ]; const output = parsers.parseNumber(input); assert.strictEqual(output, "1"); }); it('should return "0.5"', () => { - const input = "0.5"; + const input = [ + { + type: "Number", + value: "0.5" + } + ]; const output = parsers.parseNumber(input); assert.strictEqual(output, "0.5"); }); it('should return "0.5"', () => { - const input = ".5"; + const input = [ + { + type: "Number", + value: ".5" + } + ]; const output = parsers.parseNumber(input); assert.strictEqual(output, "0.5"); }); - it("should return calculated value", () => { - const input = "calc(2 / 3)"; - const output = parsers.parseLength(input); - - assert.strictEqual(output, "calc(0.666667)"); - }); - it("should return undefined", () => { - const input = "-50"; + const input = [ + { + type: "Number", + value: "-50" + } + ]; const output = parsers.parseNumber(input, { min: 0, max: 100 @@ -303,7 +348,12 @@ describe("parseNumber", () => { }); it("should return undefined", () => { - const input = "150"; + const input = [ + { + type: "Number", + value: "150" + } + ]; const output = parsers.parseNumber(input, { min: 0, max: 100 @@ -313,7 +363,12 @@ describe("parseNumber", () => { }); it("should return clamped value", () => { - const input = "-50"; + const input = [ + { + type: "Number", + value: "-50" + } + ]; const output = parsers.parseNumber(input, { min: 0, max: 100, @@ -324,7 +379,12 @@ describe("parseNumber", () => { }); it("should return clamped value", () => { - const input = "150"; + const input = [ + { + type: "Number", + value: "150" + } + ]; const output = parsers.parseNumber(input, { min: 0, max: 100, @@ -336,57 +396,77 @@ describe("parseNumber", () => { }); describe("parseLength", () => { - it("should return empty string", () => { + it("should return undefined", () => { const input = ""; const output = parsers.parseLength(input); - assert.strictEqual(output, ""); + assert.strictEqual(output, undefined); }); it("should return undefined", () => { - const input = "100"; + const input = []; const output = parsers.parseLength(input); assert.strictEqual(output, undefined); }); - it("should return value", () => { - const input = "10px"; - const output = parsers.parseLength(input); - - assert.strictEqual(output, "10px"); - }); - - it("should return value as is", () => { - const input = "var(/* comment */ --foo)"; + it("should return undefined", () => { + const input = [ + { + type: "Percentage", + value: "100" + } + ]; const output = parsers.parseLength(input); - assert.strictEqual(output, "var(/* comment */ --foo)"); + assert.strictEqual(output, undefined); }); - it("should return calculated value", () => { - const input = "calc(2em / 3)"; + it("should return undefined", () => { + const input = [ + { + type: "Number", + value: "100" + } + ]; const output = parsers.parseLength(input); - assert.strictEqual(output, "calc(0.666667em)"); + assert.strictEqual(output, undefined); }); - it("should return serialized value", () => { - const input = "calc(10px + 20%)"; + it("should return value", () => { + const input = [ + { + type: "Number", + value: "0" + } + ]; const output = parsers.parseLength(input); - assert.strictEqual(output, "calc(20% + 10px)"); + assert.strictEqual(output, "0px"); }); - it("should return serialized value", () => { - const input = "calc(100vh + 10px)"; + it("should return value", () => { + const input = [ + { + type: "Dimension", + unit: "px", + value: "10" + } + ]; const output = parsers.parseLength(input); - assert.strictEqual(output, "calc(10px + 100vh)"); + assert.strictEqual(output, "10px"); }); it("should return undefined", () => { - const input = "-50px"; + const input = [ + { + type: "Dimension", + unit: "px", + value: "-50" + } + ]; const output = parsers.parseLength(input, { min: 0, max: 100 @@ -396,7 +476,13 @@ describe("parseLength", () => { }); it("should return undefined", () => { - const input = "150px"; + const input = [ + { + type: "Dimension", + unit: "px", + value: "150" + } + ]; const output = parsers.parseLength(input, { min: 0, max: 100 @@ -406,7 +492,13 @@ describe("parseLength", () => { }); it("should return clamped value", () => { - const input = "-50px"; + const input = [ + { + type: "Dimension", + unit: "px", + value: "-50" + } + ]; const output = parsers.parseLength(input, { min: 0, max: 100, @@ -417,7 +509,13 @@ describe("parseLength", () => { }); it("should return clamped value", () => { - const input = "150px"; + const input = [ + { + type: "Dimension", + unit: "px", + value: "150" + } + ]; const output = parsers.parseLength(input, { min: 0, max: 100, @@ -429,50 +527,64 @@ describe("parseLength", () => { }); describe("parsePercent", () => { - it("should return empty string", () => { + it("should return undefined", () => { const input = ""; const output = parsers.parsePercent(input); - assert.strictEqual(output, ""); - }); - - it("should return empty string", () => { - const input = "100"; - const output = parsers.parsePercent(input); - assert.strictEqual(output, undefined); }); - it("should return value", () => { - const input = "10%"; + it("should return undefined", () => { + const input = []; const output = parsers.parsePercent(input); - assert.strictEqual(output, "10%"); + assert.strictEqual(output, undefined); }); - it("should return value as is", () => { - const input = "var(/* comment */ --foo)"; + it("should return undefined", () => { + const input = [ + { + type: "Dimension", + unit: "px", + value: "100" + } + ]; const output = parsers.parsePercent(input); - assert.strictEqual(output, "var(/* comment */ --foo)"); + assert.strictEqual(output, undefined); }); - it("should return calculated value", () => { - const input = "calc(100% / 3)"; + it("should return undefined", () => { + const input = [ + { + type: "Number", + value: "100" + } + ]; const output = parsers.parsePercent(input); - assert.strictEqual(output, "calc(33.3333%)"); + assert.strictEqual(output, undefined); }); - it("should return serialized value", () => { - const input = "calc(10px + 20%)"; + it("should return value", () => { + const input = [ + { + type: "Percentage", + value: "10" + } + ]; const output = parsers.parsePercent(input); - assert.strictEqual(output, "calc(20% + 10px)"); + assert.strictEqual(output, "10%"); }); it("should return undefined", () => { - const input = "-50%"; + const input = [ + { + type: "Percentage", + value: "-50" + } + ]; const output = parsers.parsePercent(input, { min: 0, max: 100 @@ -482,7 +594,12 @@ describe("parsePercent", () => { }); it("should return undefined", () => { - const input = "150%"; + const input = [ + { + type: "Percentage", + value: "150" + } + ]; const output = parsers.parsePercent(input, { min: 0, max: 100 @@ -492,7 +609,12 @@ describe("parsePercent", () => { }); it("should return clamped value", () => { - const input = "-50%"; + const input = [ + { + type: "Percentage", + value: "-50" + } + ]; const output = parsers.parsePercent(input, { min: 0, max: 100, @@ -503,7 +625,12 @@ describe("parsePercent", () => { }); it("should return clamped value", () => { - const input = "150%"; + const input = [ + { + type: "Percentage", + value: "150" + } + ]; const output = parsers.parsePercent(input, { min: 0, max: 100, @@ -515,85 +642,102 @@ describe("parsePercent", () => { }); describe("parseMeasurement", () => { - it("should return empty string", () => { + it("should return undefined", () => { const input = ""; const output = parsers.parseMeasurement(input); - assert.strictEqual(output, ""); - }); - - it("should return empty string", () => { - const input = "100"; - const output = parsers.parseMeasurement(input); - assert.strictEqual(output, undefined); }); - it("should return value with em unit", () => { - const input = "1em"; - const output = parsers.parseMeasurement(input); - - assert.strictEqual(output, "1em"); - }); - - it("should return value with percent", () => { - const input = "100%"; + it("should return undefined", () => { + const input = []; const output = parsers.parseMeasurement(input); - assert.strictEqual(output, "100%"); + assert.strictEqual(output, undefined); }); - it("should return value as is", () => { - const input = "var(/* comment */ --foo)"; + it("should return undefined", () => { + const input = [ + { + type: "Number", + value: "100" + } + ]; const output = parsers.parseMeasurement(input); - assert.strictEqual(output, "var(/* comment */ --foo)"); + assert.strictEqual(output, undefined); }); - it("should return calculated value", () => { - const input = "calc(2em / 3)"; + it("should return undefined", () => { + const input = [ + { + type: "Number", + value: "100" + } + ]; const output = parsers.parseMeasurement(input); - assert.strictEqual(output, "calc(0.666667em)"); + assert.strictEqual(output, undefined); }); - it("should return calculated value", () => { - const input = "calc(100% / 3)"; + it("should return undefined", () => { + const input = [ + { + type: "Dimension", + unit: "deg", + value: "180" + } + ]; const output = parsers.parseMeasurement(input); - assert.strictEqual(output, "calc(33.3333%)"); + assert.strictEqual(output, undefined); }); - it("should return serialized value", () => { - const input = "calc(10px + 20%)"; + it("should return value with em unit", () => { + const input = [ + { + type: "Dimension", + unit: "em", + value: "1" + } + ]; const output = parsers.parseMeasurement(input); - assert.strictEqual(output, "calc(20% + 10px)"); + assert.strictEqual(output, "1em"); }); - it("should return serialized value", () => { - const input = "calc(100vh + 10px)"; + it("should return value with percent", () => { + const input = [ + { + type: "Percentage", + value: "100" + } + ]; const output = parsers.parseMeasurement(input); - assert.strictEqual(output, "calc(10px + 100vh)"); + assert.strictEqual(output, "100%"); }); it("should return 0px for 0", () => { - const input = 0; - const output = parsers.parseMeasurement(input); - - assert.strictEqual(output, "0px"); - }); - - it('should return 0px for "0"', () => { - const input = "0"; + const input = [ + { + type: "Number", + value: "0" + } + ]; const output = parsers.parseMeasurement(input); assert.strictEqual(output, "0px"); }); it("should return undefined", () => { - const input = "-1em"; + const input = [ + { + type: "Dimension", + unit: "em", + value: "-1" + } + ]; const output = parsers.parseMeasurement(input, { min: 0, max: 1 @@ -603,7 +747,13 @@ describe("parseMeasurement", () => { }); it("should return undefined", () => { - const input = "1.1em"; + const input = [ + { + type: "Dimension", + unit: "em", + value: "1.1" + } + ]; const output = parsers.parseMeasurement(input, { min: 0, max: 1 @@ -613,7 +763,13 @@ describe("parseMeasurement", () => { }); it("should return clamped value", () => { - const input = "-1em"; + const input = [ + { + type: "Dimension", + unit: "em", + value: "-1" + } + ]; const output = parsers.parseMeasurement(input, { min: 0, max: 1, @@ -624,7 +780,13 @@ describe("parseMeasurement", () => { }); it("should return clamped value", () => { - const input = "1.1em"; + const input = [ + { + type: "Dimension", + unit: "em", + value: "1.1" + } + ]; const output = parsers.parseMeasurement(input, { min: 0, max: 1, @@ -636,302 +798,382 @@ describe("parseMeasurement", () => { }); describe("parseAngle", () => { - it("should return empty string", () => { + it("should return undefined", () => { const input = ""; const output = parsers.parseAngle(input); - assert.strictEqual(output, ""); + assert.strictEqual(output, undefined); + }); + + it("should return undefined", () => { + const input = []; + const output = parsers.parseAngle(input); + + assert.strictEqual(output, undefined); + }); + + it("should return undefined", () => { + const input = [ + { + type: "Perecentage", + value: "90" + } + ]; + const output = parsers.parseAngle(input); + + assert.strictEqual(output, undefined); }); it("should return undefined", () => { - const input = "90"; + const input = [ + { + type: "Number", + value: "90" + } + ]; const output = parsers.parseAngle(input); assert.strictEqual(output, undefined); }); + it('should return 0deg for "0"', () => { + const input = [ + { + type: "Number", + value: "0" + } + ]; + const output = parsers.parseAngle(input); + + assert.strictEqual(output, "0deg"); + }); + it("should return value with deg unit", () => { - const input = "90deg"; + const input = [ + { + type: "Dimension", + unit: "deg", + value: "90" + } + ]; const output = parsers.parseAngle(input); assert.strictEqual(output, "90deg"); }); it("should return value with deg unit", () => { - const input = "450deg"; + const input = [ + { + type: "Dimension", + unit: "deg", + value: "450" + } + ]; const output = parsers.parseAngle(input); assert.strictEqual(output, "450deg"); }); it("should return value with deg unit", () => { - const input = "-90deg"; + const input = [ + { + type: "Dimension", + unit: "deg", + value: "-90" + } + ]; const output = parsers.parseAngle(input); assert.strictEqual(output, "-90deg"); }); - it("should return value with deg unit", () => { - const input = "100grad"; + it("should return value with grad unit", () => { + const input = [ + { + type: "Dimension", + unit: "grad", + value: "100" + } + ]; const output = parsers.parseAngle(input); assert.strictEqual(output, "100grad"); }); - it("should return value with deg unit", () => { - const input = "500grad"; + it("should return value with grad unit", () => { + const input = [ + { + type: "Dimension", + unit: "grad", + value: "500" + } + ]; const output = parsers.parseAngle(input); assert.strictEqual(output, "500grad"); }); it("should return value with deg unit", () => { - const input = "-100grad"; + const input = [ + { + type: "Dimension", + unit: "grad", + value: "-100" + } + ]; const output = parsers.parseAngle(input); assert.strictEqual(output, "-100grad"); }); it("should return value with rad unit", () => { - const input = "1.57rad"; + const input = [ + { + type: "Dimension", + unit: "rad", + value: "1.57" + } + ]; const output = parsers.parseAngle(input); assert.strictEqual(output, "1.57rad"); }); it("should return value with rad unit", () => { - const input = "-1.57rad"; + const input = [ + { + type: "Dimension", + unit: "rad", + value: "-1.57" + } + ]; const output = parsers.parseAngle(input); assert.strictEqual(output, "-1.57rad"); }); it("should return value with turn unit", () => { - const input = "0.25turn"; - const output = parsers.parseAngle(input, { - min: 0 - }); + const input = [ + { + type: "Dimension", + unit: "turn", + value: "0.25" + } + ]; + const output = parsers.parseAngle(input); assert.strictEqual(output, "0.25turn"); }); it("should return value with turn unit", () => { - const input = "-0.25turn"; + const input = [ + { + type: "Dimension", + unit: "turn", + value: "-0.25" + } + ]; const output = parsers.parseAngle(input); assert.strictEqual(output, "-0.25turn"); }); +}); - it("should return value as is", () => { - const input = "var(/* comment */ --foo)"; - const output = parsers.parseAngle(input); +describe("parseUrl", () => { + it("should return undefined", () => { + const input = ""; + const output = parsers.parseUrl(input); - assert.strictEqual(output, "var(/* comment */ --foo)"); - }); - - it("should return calculated value", () => { - const input = "calc(90deg / 3)"; - const output = parsers.parseAngle(input); - - assert.strictEqual(output, "calc(30deg)"); - }); - - it('should return 0deg for "0"', () => { - const input = "0"; - const output = parsers.parseAngle(input); - - assert.strictEqual(output, "0deg"); - }); -}); - -describe("parseUrl", () => { - it("should return empty string", () => { - const input = ""; - const output = parsers.parseUrl(input); - - assert.strictEqual(output, ""); + assert.strictEqual(output, undefined); }); it("should return undefined", () => { - const input = "url(var(--foo))"; + const input = []; const output = parsers.parseUrl(input); assert.strictEqual(output, undefined); }); - it("should return quoted url string", () => { - const input = "url(sample.png)"; + it("should return undefined", () => { + const input = [ + { + type: "String", + value: "foo" + } + ]; const output = parsers.parseUrl(input); - assert.strictEqual(output, 'url("sample.png")'); + assert.strictEqual(output, undefined); }); - it("should return quoted url string", () => { - const input = "url('sample.png')"; + it("should return quoted empty url string", () => { + const input = [ + { + type: "Url", + value: "" + } + ]; const output = parsers.parseUrl(input); - assert.strictEqual(output, 'url("sample.png")'); + assert.strictEqual(output, 'url("")'); }); it("should return quoted url string", () => { - const input = 'url("sample.png")'; + const input = [ + { + type: "Url", + value: "sample.png" + } + ]; const output = parsers.parseUrl(input); assert.strictEqual(output, 'url("sample.png")'); }); - it("should return quoted url string without escape", () => { - const input = "url(sample\\-escaped.png)"; - const output = parsers.parseUrl(input); - - assert.strictEqual(output, 'url("sample-escaped.png")'); - }); - it("should return quoted url string with escape", () => { - const input = "url(sample\\\\-escaped.png)"; + const input = [ + { + type: "Url", + value: "sample\\\\-escaped.png" + } + ]; const output = parsers.parseUrl(input); assert.strictEqual(output, 'url("sample\\-escaped.png")'); }); - it("should return undefined", () => { - const input = "url(sample unescaped -space.png)"; - const output = parsers.parseUrl(input); - - assert.strictEqual(output, undefined); - }); - it("should return quoted url string without escape", () => { - const input = "url(sample\\ escaped\\ -space.png)"; + const input = [ + { + type: "Url", + value: "sample escaped -space.png" + } + ]; const output = parsers.parseUrl(input); assert.strictEqual(output, 'url("sample escaped -space.png")'); }); - it("should return undefined", () => { - const input = "url(sample\tunescaped\t-tab.png)"; - const output = parsers.parseUrl(input); - - assert.strictEqual(output, undefined); - }); - it("should return quoted url string without escape", () => { - const input = "url(sample\\\tescaped\\\t-tab.png)"; + const input = [ + { + type: "Url", + value: "sample\tescaped\t-tab.png" + } + ]; const output = parsers.parseUrl(input); assert.strictEqual(output, 'url("sample\tescaped\t-tab.png")'); }); - it("should return undefined", () => { - const input = "url(sample\nunescaped\n-lf.png)"; - const output = parsers.parseUrl(input); - - assert.strictEqual(output, undefined); - }); - - it("should return undefined", () => { - const input = "url(sample\\\nescaped\\\n-lf.png)"; - const output = parsers.parseUrl(input); - - assert.strictEqual(output, undefined); - }); - - it("should return undefined", () => { - const input = "url(sample'unescaped'-quote.png)"; - const output = parsers.parseUrl(input); - - assert.strictEqual(output, undefined); - }); - it("should return quoted url string without escape", () => { - const input = "url(sample\\'escaped\\'-quote.png)"; + const input = [ + { + type: "Url", + value: "sample'escaped'-quote.png" + } + ]; const output = parsers.parseUrl(input); // prettier-ignore assert.strictEqual(output, "url(\"sample'escaped'-quote.png\")"); }); - it("should return undefined", () => { - const input = 'url(sample"unescaped"-double-quote.png)'; - const output = parsers.parseUrl(input); - - assert.strictEqual(output, undefined); - }); - it("should return quoted url string with escape", () => { - const input = 'url(sample\\"escaped\\"-double-quote.png)'; + const input = [ + { + type: "Url", + value: 'sample"escaped"-double-quote.png' + } + ]; const output = parsers.parseUrl(input); assert.strictEqual(output, 'url("sample\\"escaped\\"-double-quote.png")'); }); - - it("should return quoted empty url string", () => { - const input = "url()"; - const output = parsers.parseUrl(input); - - assert.strictEqual(output, 'url("")'); - }); - - it("should return quoted empty url string", () => { - const input = 'url("")'; - const output = parsers.parseUrl(input); - - assert.strictEqual(output, 'url("")'); - }); }); describe("parseString", () => { - it("should return empty string", () => { + it("should return undefined", () => { const input = ""; const output = parsers.parseString(input); - assert.strictEqual(output, ""); + assert.strictEqual(output, undefined); }); it("should return undefined", () => { - const input = "foo"; + const input = []; const output = parsers.parseString(input); assert.strictEqual(output, undefined); }); - it("should return quoted string", () => { - const input = "'foo bar\""; + it("should return undefined", () => { + const input = [ + { + type: "Url", + value: "exmaple.com" + } + ]; const output = parsers.parseString(input); - assert.strictEqual(output, '"foo bar\\""'); + assert.strictEqual(output, undefined); }); it("should return quoted string", () => { - const input = "'foo bar'"; + const input = [ + { + type: "String", + value: "foo bar" + } + ]; const output = parsers.parseString(input); assert.strictEqual(output, '"foo bar"'); }); it("should return quoted string", () => { - const input = '"foo bar"'; + const input = [ + { + type: "String", + value: "'foo bar\"" + } + ]; const output = parsers.parseString(input); - assert.strictEqual(output, '"foo bar"'); + assert.strictEqual(output, '"\'foo bar\\""'); }); it("should return quoted string", () => { - const input = '"foo \ bar"'; + const input = [ + { + type: "String", + value: "foo bar" + } + ]; const output = parsers.parseString(input); assert.strictEqual(output, '"foo bar"'); }); it("should return quoted string", () => { - const input = '"foo \\\\ bar"'; + const input = [ + { + type: "String", + value: "foo \\ bar" + } + ]; const output = parsers.parseString(input); assert.strictEqual(output, '"foo \\ bar"'); }); it("should return quoted string", () => { - const input = "'foo \"bar\"'"; + const input = [ + { + type: "String", + value: 'foo "bar"' + } + ]; const output = parsers.parseString(input); assert.strictEqual(output, '"foo \\"bar\\""'); @@ -939,386 +1181,208 @@ describe("parseString", () => { }); describe("parseColor", () => { - it("should return empty string", () => { + it("should return undefined", () => { const input = ""; const output = parsers.parseColor(input); - assert.strictEqual(output, ""); - }); - - it("should return inherit", () => { - const input = "inherit"; - const output = parsers.parseColor(input); - assert.strictEqual(output, undefined); }); - it("should return lower cased keyword for system color", () => { - const input = "CanvasText"; + it("should return system color", () => { + const input = [ + { + type: "Identifier", + name: "canvastext" + } + ]; const output = parsers.parseColor(input); assert.strictEqual(output, "canvastext"); }); it("should convert hsl to rgb values", () => { - const input = "hsla(0, 1%, 2%)"; + const input = [ + { + type: "Function", + name: "hsla", + value: "0, 1%, 2%" + } + ]; const output = parsers.parseColor(input); assert.strictEqual(output, "rgb(5, 5, 5)"); }); it("should convert hsla to rgba values", () => { - const input = "hsla(0, 1%, 2%, 0.5)"; + const input = [ + { + type: "Function", + name: "hsla", + value: "0, 1%, 2%, 0.5" + } + ]; const output = parsers.parseColor(input); assert.strictEqual(output, "rgba(5, 5, 5, 0.5)"); }); it("should convert not zero hsl with non zero hue 120 to rgb(0, 255, 0)", () => { - const input = "hsl(120, 100%, 50%)"; + const input = [ + { + type: "Function", + name: "hsl", + value: "120, 100%, 50%" + } + ]; const output = parsers.parseColor(input); assert.strictEqual(output, "rgb(0, 255, 0)"); }); it("should convert not zero hsl with non zero hue 240 to rgb(0, 0, 255)", () => { - const input = "hsl(240, 100%, 50%)"; + const input = [ + { + type: "Function", + name: "hsl", + value: "240, 100%, 50%" + } + ]; const output = parsers.parseColor(input); assert.strictEqual(output, "rgb(0, 0, 255)"); }); it("should convert modern rgb to rgb values", () => { - const input = "rgb(128 0 128 / 1)"; + const input = [ + { + type: "Function", + name: "rgb", + value: "128 0 128 / 1" + } + ]; const output = parsers.parseColor(input); assert.strictEqual(output, "rgb(128, 0, 128)"); }); it("should convert modern rgb with none values to rgb values", () => { - const input = "rgb(128 0 none)"; + const input = [ + { + type: "Function", + name: "rgb", + value: "128 0 none" + } + ]; const output = parsers.parseColor(input); assert.strictEqual(output, "rgb(128, 0, 0)"); }); it("should convert modern rgba to rgba values", () => { - const input = "rgba(127.5 0 127.5 / .5)"; + const input = [ + { + type: "Function", + name: "rgba", + value: "127.5 0 127.5 / .5" + } + ]; const output = parsers.parseColor(input); assert.strictEqual(output, "rgba(128, 0, 128, 0.5)"); }); it("should normalize lab values", () => { - const input = "lab(46.2775% -47.5621 48.5837 / 1.0)"; // green + const input = [ + { + type: "Function", + name: "lab", + value: "46.2775% -47.5621 48.5837 / 1.0" + } + ]; const output = parsers.parseColor(input); assert.strictEqual(output, "lab(46.2775 -47.5621 48.5837)"); }); it("should normalize color function values", () => { - const input = "color(srgb 0 .5 0 / 1.0)"; + const input = [ + { + type: "Function", + name: "color", + value: "srgb 0 .5 0 / 1.0" + } + ]; const output = parsers.parseColor(input); assert.strictEqual(output, "color(srgb 0 0.5 0)"); }); it("should normalize color-mix values", () => { - const input = "color-mix(in srgb, rgb(255 0 0), #0000ff 40%)"; + const input = [ + { + type: "Function", + name: "color-mix", + value: "in srgb, rgb(255 0 0), #0000ff 40%" + } + ]; const output = parsers.parseColor(input); assert.strictEqual(output, "color-mix(in srgb, rgb(255, 0, 0) 60%, rgb(0, 0, 255))"); }); - - it("should not remove comments, trim or lower case letters if var() is used", () => { - const input = "var( --custom-Color /* comment */)"; - const output = parsers.parseColor(input); - - assert.strictEqual(output, "var( --custom-Color /* comment */)"); - }); - - it("should output transparent keyword", () => { - const input = "transparent"; - const output = parsers.parseColor(input); - - assert.strictEqual(output, "transparent"); - }); - - it("should return value as is with var()", () => { - const input = "rgb(var(--my-var, 0, 0, 0))"; - const output = parsers.parseColor(input); - - assert.strictEqual(output, "rgb(var(--my-var, 0, 0, 0))"); - }); -}); - -describe("parseKeyword", () => { - it("should return value", () => { - const input = "inherit"; - const output = parsers.parseKeyword(input); - - assert.strictEqual(output, "inherit"); - }); - - it("should return value", () => { - const input = "foo"; - const output = parsers.parseKeyword(input, ["foo", "bar"]); - - assert.strictEqual(output, "foo"); - }); - - it("should return value", () => { - const input = "Bar"; - const output = parsers.parseKeyword(input, ["foo", "bar"]); - - assert.strictEqual(output, "bar"); - }); - - it("should return undefined", () => { - const input = "baz"; - const output = parsers.parseKeyword(input, ["foo", "bar"]); - - assert.strictEqual(output, undefined); - }); }); describe("parseImage", () => { - it("should return empty string", () => { - const input = ""; - const output = parsers.parseImage(input); - - assert.strictEqual(output, ""); - }); - it("should return undefined", () => { - const input = "foo"; + const input = ""; const output = parsers.parseImage(input); assert.strictEqual(output, undefined); }); it("should return undefined", () => { - const input = "none"; + const input = []; const output = parsers.parseImage(input); assert.strictEqual(output, undefined); }); it("should return undefined", () => { - const input = "inherit"; + const input = [ + { + type: "Identifier", + name: "none" + } + ]; const output = parsers.parseImage(input); assert.strictEqual(output, undefined); }); it("should return undefined for negative radii", () => { - const input = "radial-gradient(circle -10px at center, red, blue)"; + const input = [ + { + type: "Function", + name: "radial-gradient", + value: "circle -10px at center, red, blue" + } + ]; const output = parsers.parseImage(input); assert.strictEqual(output, undefined); }); it("should return value", () => { - const input = "url(example.png)"; - const output = parsers.parseImage(input); - - assert.strictEqual(output, 'url("example.png")'); - }); - - it("should return value", () => { - const input = "url(example.png)"; - const output = parsers.parseImage(input); - - assert.strictEqual(output, 'url("example.png")'); - }); - - it("should return value", () => { - const input = "linear-gradient(green, blue)"; + const input = [ + { + type: "Function", + name: "linear-gradient", + value: "green, blue" + } + ]; const output = parsers.parseImage(input); assert.strictEqual(output, "linear-gradient(green, blue)"); }); - - it("should return value as is if var() is included", () => { - const input = "radial-gradient(transparent, /* comment */ var(--custom-color))"; - const output = parsers.parseImage(input); - - assert.strictEqual(output, "radial-gradient(transparent, /* comment */ var(--custom-color))"); - }); - - it("should return undefined if value is not image type", () => { - const input = "red"; - const output = parsers.parseImage(input); - - assert.strictEqual(output, undefined); - }); - - it("should return value even if value is not gradient but contains var()", () => { - const input = "rgb(var(--my-var, 0, 0, 0))"; - const output = parsers.parseImage(input); - - assert.strictEqual(output, "rgb(var(--my-var, 0, 0, 0))"); - }); -}); - -describe("parseFunction", () => { - it("should return undefined for keyword", () => { - const input = "inherit"; - const output = parsers.parseFunction(input); - - assert.strictEqual(output, undefined); - }); - - it("should return object with null name and empty value for empty string", () => { - const input = ""; - const output = parsers.parseFunction(input); - - assert.deepEqual(output, { - name: null, - value: "", - hasVar: false, - raw: "" - }); - }); - - it("should return undefined for unmatched value (starting with digit)", () => { - const input = "123go(foo)"; - const output = parsers.parseFunction(input); - - assert.strictEqual(output, undefined); - }); - - it("should return object with hasVar: true", () => { - const input = "var(--foo)"; - const output = parsers.parseFunction(input); - - assert.deepEqual(output, { - name: "var", - value: "--foo", - hasVar: true, - raw: "var(--foo)" - }); - }); - - it("should return object with hasVar: true", () => { - const input = "translate(var(--foo))"; - const output = parsers.parseFunction(input); - - assert.deepEqual(output, { - name: "translate", - value: "var(--foo)", - hasVar: true, - raw: "translate(var(--foo))" - }); - }); - - it("should return object", () => { - const input = "translate(0)"; - const output = parsers.parseFunction(input); - - assert.deepEqual(output, { - name: "translate", - value: "0", - hasVar: false, - raw: "translate(0)" - }); - }); - - it("should return object", () => { - const input = "translate(42px)"; - const output = parsers.parseFunction(input); - - assert.deepEqual(output, { - name: "translate", - value: "42px", - hasVar: false, - raw: "translate(42px)" - }); - }); - - it("should return object", () => { - const input = "translate( 100px, 200px )"; - const output = parsers.parseFunction(input); - - assert.deepEqual(output, { - name: "translate", - value: "100px, 200px", - hasVar: false, - raw: "translate( 100px, 200px )" - }); - }); - - it("should return object", () => { - const input = "translateX(100px)"; - const output = parsers.parseFunction(input); - - assert.deepEqual(output, { - name: "translateX", - value: "100px", - hasVar: false, - raw: "translateX(100px)" - }); - }); - - it("should return object", () => { - const input = "translateY(100px)"; - const output = parsers.parseFunction(input); - - assert.deepEqual(output, { - name: "translateY", - value: "100px", - hasVar: false, - raw: "translateY(100px)" - }); - }); - - it("should return object", () => { - const input = "translateZ(100px)"; - const output = parsers.parseFunction(input); - - assert.deepEqual(output, { - name: "translateZ", - value: "100px", - hasVar: false, - raw: "translateZ(100px)" - }); - }); - - it("should return object", () => { - const input = "translate3d(42px, -62px, -135px)"; - const output = parsers.parseFunction(input); - - assert.deepEqual(output, { - name: "translate3d", - value: "42px, -62px, -135px", - hasVar: false, - raw: "translate3d(42px, -62px, -135px)" - }); - }); - - it("should return object", () => { - const input = "drop-shadow(30px 10px 4px #4444dd)"; - const output = parsers.parseFunction(input); - - assert.deepEqual(output, { - name: "drop-shadow", - value: "30px 10px 4px #4444dd", - hasVar: false, - raw: "drop-shadow(30px 10px 4px #4444dd)" - }); - }); - - it("should return object", () => { - const input = "foo-bar-baz(qux)"; - const output = parsers.parseFunction(input); - - assert.deepEqual(output, { - name: "foo-bar-baz", - value: "qux", - hasVar: false, - raw: "foo-bar-baz(qux)" - }); - }); }); describe("parseShorthand", () => { From 80c129825b7fb73ea03bd7905e068105ae3c4589 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Fri, 22 Aug 2025 07:13:24 +0900 Subject: [PATCH 39/41] Update normalizeBorderProperties function --- lib/utils/normalizeBorders.js | 77 +++++--------------------------- test/CSSStyleDeclaration.test.js | 38 +++++++++++++++- 2 files changed, 47 insertions(+), 68 deletions(-) diff --git a/lib/utils/normalizeBorders.js b/lib/utils/normalizeBorders.js index 2aa56ab7..e93c7c96 100644 --- a/lib/utils/normalizeBorders.js +++ b/lib/utils/normalizeBorders.js @@ -686,87 +686,30 @@ module.exports.normalizeBorderProperties = function (properties) { for (const line of lines) { const lineProperty = `${name}-${line}`; if (properties.has(lineProperty)) { - const positionValues = []; for (const position of positions) { const positionProperty = `${name}-${position}`; - if (properties.has(positionProperty)) { - const positionItem = properties.get(positionProperty); - positionValues.push(positionItem.value); - } - properties.delete(`${positionProperty}-${line}`); - } - if (positionValues.length === 4) { - const lineItem = properties.get(lineProperty); - if (positionValues.every((positionValue) => positionValue === lineItem.value)) { - for (const position of positions) { - properties.delete(`${name}-${position}`); - } - } + const longhandProperty = `${name}-${position}-${line}`; + properties.delete(positionProperty); + properties.delete(longhandProperty); } } } - const initialValuePositionProperties = []; - const omitPositionProperties = []; for (const position of positions) { const positionProperty = `${name}-${position}`; if (properties.has(positionProperty)) { - const positionValue = properties.get(positionProperty); - if (positionValue.value === firstInitialValue) { - initialValuePositionProperties.push(positionProperty); - } - let omitPositionProperty = true; - for (const line of lines) { - const lineProperty = `${name}-${line}`; - if (!properties.has(lineProperty)) { - omitPositionProperty = false; - } - properties.delete(`${name}-${position}-${line}`); - } - if (omitPositionProperty) { - omitPositionProperties.push(positionProperty); - } - } else { - const lineValueItems = new Map(); + const longhandProperties = []; for (const line of lines) { const longhandProperty = `${name}-${position}-${line}`; - if (properties.has(longhandProperty)) { - const longhandItem = properties.get(longhandProperty); - lineValueItems.set(`${name}-${line}`, longhandItem.value); - } + longhandProperties.push(longhandProperty); } - if (lineValueItems.size === 3) { - const obj = { - [firstInitialKey]: firstInitialValue - }; - for (const line of lines) { - const lineProperty = `${name}-${line}`; - const initialValue = border.initialValues.get(lineProperty); - const lineValue = lineValueItems.get(lineProperty); - if (lineValue !== initialValue) { - obj[lineProperty] = lineValue; - if (obj[firstInitialKey] && obj[firstInitialKey] === firstInitialValue) { - delete obj[firstInitialKey]; - } - } - properties.delete(`${name}-${position}-${line}`); + if (longhandProperties.length === 3) { + for (const longhandProperty of longhandProperties) { + properties.delete(longhandProperty); } - properties.set(positionProperty, { - property: positionProperty, - value: Object.values(obj).join(" "), - priority: null - }); + } else { + properties.delete(positionProperty); } } } - if (initialValuePositionProperties.length === 4) { - for (const positionProperty of initialValuePositionProperties) { - properties.delete(positionProperty); - } - } - if (omitPositionProperties.length === 4) { - for (const positionProperty of omitPositionProperties) { - properties.delete(positionProperty); - } - } return properties; }; diff --git a/test/CSSStyleDeclaration.test.js b/test/CSSStyleDeclaration.test.js index ccb1e02a..375222d4 100644 --- a/test/CSSStyleDeclaration.test.js +++ b/test/CSSStyleDeclaration.test.js @@ -559,6 +559,42 @@ describe("CSSStyleDeclaration", () => { assert.strictEqual(style.cssText, "border: medium;", "cssText"); }); + it("set border-style as solid and change border-top to none", () => { + const style = new CSSStyleDeclaration(); + style.borderStyle = "solid"; + style.borderTop = "none"; + assert.strictEqual(style.border, "", "border"); + assert.strictEqual(style.borderWidth, "", "border-width"); + assert.strictEqual(style.borderStyle, "none solid solid", "border-style"); + assert.strictEqual(style.borderTop, "medium", "border-top"); + assert.strictEqual(style.borderTopWidth, "medium", "border-top-width"); + assert.strictEqual(style.borderTopStyle, "none", "border-top-style"); + assert.strictEqual(style.borderImage, "", "border-image"); + assert.strictEqual( + style.cssText, + "border-style: none solid solid; border-top-width: medium; border-top-color: currentcolor;", + "cssText" + ); + }); + + it("set border-top as solid and change border-style to none", () => { + const style = new CSSStyleDeclaration(); + style.borderTop = "solid"; + style.borderStyle = "none"; + assert.strictEqual(style.border, "", "border"); + assert.strictEqual(style.borderWidth, "", "border-width"); + assert.strictEqual(style.borderStyle, "none", "border-style"); + assert.strictEqual(style.borderTop, "medium", "border-top"); + assert.strictEqual(style.borderTopWidth, "medium", "border-top-width"); + assert.strictEqual(style.borderTopStyle, "none", "border-top-style"); + assert.strictEqual(style.borderImage, "", "border-image"); + assert.strictEqual( + style.cssText, + "border-top-width: medium; border-top-color: currentcolor; border-style: none;", + "cssText" + ); + }); + it("set border-style as solid and change border-top to null", () => { const style = new CSSStyleDeclaration(); style.borderStyle = "solid"; @@ -574,7 +610,7 @@ describe("CSSStyleDeclaration", () => { assert.strictEqual(style.borderTop, "", "border-top"); assert.strictEqual(style.borderTopWidth, "", "border-top-width"); assert.strictEqual(style.borderTopStyle, "", "border-top-style"); - assert.strictEqual(style.borderImage, "", "borrder-image"); + assert.strictEqual(style.borderImage, "", "border-image"); }); it("setting border values to none should change dependent values", () => { From 2510f3cae956f233d17fbad4a6ed6160b9edfbeb Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Sat, 23 Aug 2025 05:58:25 +0900 Subject: [PATCH 40/41] Update file path --- lib/CSSStyleDeclaration.js | 9 ++- lib/allExtraProperties.js | 49 ------------ lib/parsers.js | 35 ++++++--- lib/properties/background.js | 2 +- lib/properties/backgroundImage.js | 2 +- lib/properties/backgroundPosition.js | 19 +++-- lib/properties/backgroundSize.js | 2 +- lib/properties/bottom.js | 2 +- lib/properties/clip.js | 2 +- lib/properties/flexBasis.js | 2 +- lib/properties/font.js | 2 +- lib/properties/fontSize.js | 2 +- lib/properties/height.js | 2 +- lib/properties/left.js | 2 +- lib/properties/lineHeight.js | 2 +- lib/properties/margin.js | 2 +- lib/properties/marginBottom.js | 2 +- lib/properties/marginLeft.js | 2 +- lib/properties/marginRight.js | 2 +- lib/properties/marginTop.js | 2 +- lib/properties/opacity.js | 2 +- lib/properties/padding.js | 2 +- lib/properties/paddingBottom.js | 2 +- lib/properties/paddingLeft.js | 2 +- lib/properties/paddingRight.js | 2 +- lib/properties/paddingTop.js | 2 +- lib/properties/right.js | 2 +- lib/properties/top.js | 2 +- lib/properties/width.js | 2 +- lib/shorthandProperties.js | 30 -------- .../allExtraProperties.js} | 43 ++++++++++- .../{normalizeBorders.js => normalize.js} | 23 ++++++ test/CSSStyleDeclaration.test.js | 2 +- test/parsers.test.js | 76 +++++++++---------- 34 files changed, 169 insertions(+), 167 deletions(-) delete mode 100644 lib/allExtraProperties.js delete mode 100644 lib/shorthandProperties.js rename lib/{allWebkitProperties.js => utils/allExtraProperties.js} (73%) rename lib/utils/{normalizeBorders.js => normalize.js} (96%) diff --git a/lib/CSSStyleDeclaration.js b/lib/CSSStyleDeclaration.js index 05688a9e..8b3bce51 100644 --- a/lib/CSSStyleDeclaration.js +++ b/lib/CSSStyleDeclaration.js @@ -3,7 +3,7 @@ * https://github.com/NV/CSSOM */ "use strict"; -const allExtraProperties = require("./allExtraProperties"); + const allProperties = require("./generated/allProperties"); const implementedProperties = require("./generated/implementedProperties"); const generatedProperties = require("./generated/properties"); @@ -16,13 +16,14 @@ const { prepareValue, splitValue } = require("./parsers"); -const { shorthandProperties } = require("./shorthandProperties"); +const allExtraProperties = require("./utils/allExtraProperties"); const { dashedToCamelCase } = require("./utils/camelize"); const { borderProperties, normalizeBorderProperties, - prepareBorderProperties -} = require("./utils/normalizeBorders"); + prepareBorderProperties, + shorthandProperties +} = require("./utils/normalize"); const { getPropertyDescriptor } = require("./utils/propertyDescriptors"); const { asciiLowercase } = require("./utils/strings"); diff --git a/lib/allExtraProperties.js b/lib/allExtraProperties.js deleted file mode 100644 index f3496b01..00000000 --- a/lib/allExtraProperties.js +++ /dev/null @@ -1,49 +0,0 @@ -"use strict"; - -/** - * This file contains all implemented properties that are not a part of any - * current specifications or drafts, but are handled by browsers nevertheless. - */ - -const allWebkitProperties = require("./allWebkitProperties"); - -module.exports = new Set([ - "background-position-x", - "background-position-y", - "background-repeat-x", - "background-repeat-y", - "color-interpolation", - "color-profile", - "color-rendering", - "enable-background", - "glyph-orientation-horizontal", - "kerning", - "marker-offset", - "marks", - "pointer-events", - "shape-rendering", - "size", - "src", - "stop-color", - "stop-opacity", - "text-anchor", - "text-line-through", - "text-line-through-color", - "text-line-through-mode", - "text-line-through-style", - "text-line-through-width", - "text-overline", - "text-overline-color", - "text-overline-mode", - "text-overline-style", - "text-overline-width", - "text-rendering", - "text-underline", - "text-underline-color", - "text-underline-mode", - "text-underline-style", - "text-underline-width", - "unicode-range", - "vector-effect", - ...allWebkitProperties -]); diff --git a/lib/parsers.js b/lib/parsers.js index 6ea74fb0..03e89fb3 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -69,7 +69,7 @@ const calcNameRegEx = new RegExp(`^${CALC_FUNC_NAMES}$`); const varRegEx = /^var\(/; const varContainedRegEx = /(?<=[*/\s(])var\(/; -// Patched css-tree. +// Patched css-tree const cssTree = csstree.fork(syntaxes); // Prepare stringified value. @@ -118,7 +118,7 @@ exports.hasCalcFunc = function hasCalcFunc(val) { // @see https://github.com/asamuzaK/cssColor/blob/main/src/js/util.ts exports.splitValue = splitValue; -// Parse CSS to AST or Object. +// Parse CSS to AST. exports.parseCSS = function parseCSS(val, opt, toObject = false) { if (typeof val !== "string") { val = exports.prepareValue(val); @@ -130,6 +130,7 @@ exports.parseCSS = function parseCSS(val, opt, toObject = false) { return ast; }; +// Value is a valid property value. // Returns `false` for custom property and/or var(). exports.isValidPropertyValue = function isValidPropertyValue(prop, val) { if (typeof val !== "string") { @@ -159,7 +160,8 @@ exports.isValidPropertyValue = function isValidPropertyValue(prop, val) { return error === null && matched !== null; }; -exports.parseCalc = function parseCalc(val, opt = { format: "specifiedValue" }) { +// Simplify / resolve math functions. +exports.resolveCalc = function resolveCalc(val, opt = { format: "specifiedValue" }) { if (typeof val !== "string") { val = exports.prepareValue(val); } @@ -194,6 +196,7 @@ exports.parseCalc = function parseCalc(val, opt = { format: "specifiedValue" }) return values.join(" "); }; +// Parse shorthand properties. exports.parseShorthand = function parseShorthand(val, shorthandFor, opt = {}) { const { globalObject, preserve } = opt; if (typeof val !== "string") { @@ -217,8 +220,8 @@ exports.parseShorthand = function parseShorthand(val, shorthandFor, opt = {}) { const longhands = [...shorthandFor]; for (let part of parts) { if (exports.hasCalcFunc(part)) { - const parsedValue = exports.parseCalc(part); - part = parsedValue; + const partValue = exports.resolveCalc(part); + part = partValue; } let partValid = false; for (let i = 0; i < longhands.length; i++) { @@ -246,13 +249,13 @@ exports.parsePropertyValue = function parsePropertyValue(prop, val, opt = {}) { if (val === "" || exports.hasVarFunc(val)) { return val; } else if (exports.hasCalcFunc(val)) { - const parsedValue = exports.parseCalc(val, { + const calculatedValue = exports.resolveCalc(val, { format: "specifiedValue" }); - if (typeof parsedValue !== "string") { + if (typeof calculatedValue !== "string") { return; } - val = parsedValue; + val = calculatedValue; } const lowerCasedValue = asciiLowercase(val); if (GLOBAL_KEY.includes(lowerCasedValue)) { @@ -375,6 +378,7 @@ exports.parsePropertyValue = function parsePropertyValue(prop, val, opt = {}) { return val; }; +// Parse . exports.parseNumber = function parseNumber(val, opt = {}) { const [item] = val; const { type, value } = item ?? {}; @@ -397,6 +401,7 @@ exports.parseNumber = function parseNumber(val, opt = {}) { return `${num}`; }; +// Parse . exports.parseLength = function parseLength(val, opt = {}) { const [item] = val; const { type, value, unit } = item ?? {}; @@ -423,7 +428,8 @@ exports.parseLength = function parseLength(val, opt = {}) { } }; -exports.parsePercent = function parsePercent(val, opt = {}) { +// Parse . +exports.parsePercentage = function parsePercentage(val, opt = {}) { const [item] = val; const { type, value } = item ?? {}; if (type !== "Percentage" && !(type === "Number" && value === "0")) { @@ -448,8 +454,8 @@ exports.parsePercent = function parsePercent(val, opt = {}) { return `${num}%`; }; -// Either a length or a percent. -exports.parseMeasurement = function parseMeasurement(val, opt = {}) { +// Parse . +exports.parseLengthPercentage = function parseLengthPercentage(val, opt = {}) { const [item] = val; const { type, value, unit } = item ?? {}; if (type !== "Dimension" && type !== "Percentage" && !(type === "Number" && value === "0")) { @@ -480,6 +486,7 @@ exports.parseMeasurement = function parseMeasurement(val, opt = {}) { } }; +// Parse . exports.parseAngle = function parseAngle(val) { const [item] = val; const { type, value, unit } = item ?? {}; @@ -497,6 +504,7 @@ exports.parseAngle = function parseAngle(val) { } }; +// Parse . exports.parseUrl = function parseUrl(val) { const [item] = val; const { type, value } = item ?? {}; @@ -507,6 +515,7 @@ exports.parseUrl = function parseUrl(val) { return `url("${str}")`; }; +// Parse . exports.parseString = function parseString(val) { const [item] = val; const { type, value } = item ?? {}; @@ -517,6 +526,7 @@ exports.parseString = function parseString(val) { return `"${str}"`; }; +// Parse . exports.parseColor = function parseColor(val) { const [item] = val; const { name, type, value } = item ?? {}; @@ -555,7 +565,8 @@ exports.parseColor = function parseColor(val) { } }; -exports.parseImage = function parseImage(val) { +// Parse . +exports.parseGradient = function parseGradient(val) { const [item] = val; const { name, type, value } = item ?? {}; if (type !== "Function") { diff --git a/lib/properties/background.js b/lib/properties/background.js index 2efb7d6d..7be7b27a 100644 --- a/lib/properties/background.js +++ b/lib/properties/background.js @@ -37,7 +37,7 @@ module.exports.parse = function parse(v, opt = {}) { if (v === "") { return v; } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); + v = parsers.resolveCalc(v); } if (!parsers.isValidPropertyValue("background", v)) { return; diff --git a/lib/properties/backgroundImage.js b/lib/properties/backgroundImage.js index add73ef5..44264963 100644 --- a/lib/properties/backgroundImage.js +++ b/lib/properties/backgroundImage.js @@ -31,7 +31,7 @@ module.exports.parse = function parse(v, opt = {}) { break; } default: { - const parsedValue = parsers.parseImage(value); + const parsedValue = parsers.parseGradient(value); if (!parsedValue) { return; } diff --git a/lib/properties/backgroundPosition.js b/lib/properties/backgroundPosition.js index 9dd6ed8c..66981e40 100644 --- a/lib/properties/backgroundPosition.js +++ b/lib/properties/backgroundPosition.js @@ -27,7 +27,8 @@ module.exports.parse = function parse(v, opt = {}) { switch (value.length) { case 1: { const [part1] = value; - const val1 = part1.type === "Identifier" ? part1.name : parsers.parseMeasurement([part1]); + const val1 = + part1.type === "Identifier" ? part1.name : parsers.parseLengthPercentage([part1]); if (val1) { if (val1 === "center") { parsedValue = `${val1} ${val1}`; @@ -41,8 +42,10 @@ module.exports.parse = function parse(v, opt = {}) { } case 2: { const [part1, part2] = value; - const val1 = part1.type === "Identifier" ? part1.name : parsers.parseMeasurement([part1]); - const val2 = part2.type === "Identifier" ? part2.name : parsers.parseMeasurement([part2]); + const val1 = + part1.type === "Identifier" ? part1.name : parsers.parseLengthPercentage([part1]); + const val2 = + part2.type === "Identifier" ? part2.name : parsers.parseLengthPercentage([part2]); if (val1 && val2) { if (keywordsX.includes(val1) && keywordsY.includes(val2)) { parsedValue = `${val1} ${val2}`; @@ -65,8 +68,10 @@ module.exports.parse = function parse(v, opt = {}) { case 3: { const [part1, part2, part3] = value; const val1 = part1.type === "Identifier" && part1.name; - const val2 = part2.type === "Identifier" ? part2.name : parsers.parseMeasurement([part2]); - const val3 = part3.type === "Identifier" ? part3.name : parsers.parseMeasurement([part3]); + const val2 = + part2.type === "Identifier" ? part2.name : parsers.parseLengthPercentage([part2]); + const val3 = + part3.type === "Identifier" ? part3.name : parsers.parseLengthPercentage([part3]); if (val1 && val2 && val3) { let posX = ""; let offX = ""; @@ -114,9 +119,9 @@ module.exports.parse = function parse(v, opt = {}) { case 4: { const [part1, part2, part3, part4] = value; const val1 = part1.type === "Identifier" && part1.name; - const val2 = parsers.parseMeasurement([part2]); + const val2 = parsers.parseLengthPercentage([part2]); const val3 = part3.type === "Identifier" && part3.name; - const val4 = parsers.parseMeasurement([part4]); + const val4 = parsers.parseLengthPercentage([part4]); if (val1 && val2 && val3 && val4) { let posX = ""; let offX = ""; diff --git a/lib/properties/backgroundSize.js b/lib/properties/backgroundSize.js index 9cd8e2ae..f053a95a 100644 --- a/lib/properties/backgroundSize.js +++ b/lib/properties/backgroundSize.js @@ -33,7 +33,7 @@ module.exports.parse = function parse(v, opt = {}) { break; } default: { - const parsedValue = parsers.parseMeasurement(value); + const parsedValue = parsers.parseLengthPercentage(value); if (!parsedValue) { return; } diff --git a/lib/properties/bottom.js b/lib/properties/bottom.js index c1dd9578..f15e4345 100644 --- a/lib/properties/bottom.js +++ b/lib/properties/bottom.js @@ -25,7 +25,7 @@ module.exports.parse = function parse(v, opt = {}) { return name; } default: { - return parsers.parseMeasurement(value); + return parsers.parseLengthPercentage(value); } } } else if (typeof value === "string") { diff --git a/lib/properties/clip.js b/lib/properties/clip.js index ba927200..bb2f6f9f 100644 --- a/lib/properties/clip.js +++ b/lib/properties/clip.js @@ -23,7 +23,7 @@ module.exports.parse = function parse(v, opt = {}) { const parsedValues = []; for (const item of values) { const parsedValue = parsers.parseCSS(item, { context: "value" }, true); - const val = parsers.parseMeasurement(parsedValue.children); + const val = parsers.parseLengthPercentage(parsedValue.children); if (val) { parsedValues.push(val); } else { diff --git a/lib/properties/flexBasis.js b/lib/properties/flexBasis.js index 08d857ce..fc3e62a8 100644 --- a/lib/properties/flexBasis.js +++ b/lib/properties/flexBasis.js @@ -25,7 +25,7 @@ module.exports.parse = function parse(v, opt = {}) { return name; } default: { - return parsers.parseMeasurement(value); + return parsers.parseLengthPercentage(value); } } } else if (typeof value === "string") { diff --git a/lib/properties/font.js b/lib/properties/font.js index 2a5116bf..9fd75a09 100644 --- a/lib/properties/font.js +++ b/lib/properties/font.js @@ -22,7 +22,7 @@ module.exports.parse = function parse(v, opt = {}) { if (v === "") { return v; } else if (parsers.hasCalcFunc(v)) { - v = parsers.parseCalc(v); + v = parsers.resolveCalc(v); } if (!parsers.isValidPropertyValue("font", v)) { return; diff --git a/lib/properties/fontSize.js b/lib/properties/fontSize.js index a8f9dae2..dcd3d3c6 100644 --- a/lib/properties/fontSize.js +++ b/lib/properties/fontSize.js @@ -25,7 +25,7 @@ module.exports.parse = function parse(v, opt = {}) { return name; } default: { - return parsers.parseMeasurement(value, { + return parsers.parseLengthPercentage(value, { min: 0 }); } diff --git a/lib/properties/height.js b/lib/properties/height.js index 34f7dd95..f76c420f 100644 --- a/lib/properties/height.js +++ b/lib/properties/height.js @@ -25,7 +25,7 @@ module.exports.parse = function parse(v, opt = {}) { return name; } default: { - return parsers.parseMeasurement(value); + return parsers.parseLengthPercentage(value); } } } else if (typeof value === "string") { diff --git a/lib/properties/left.js b/lib/properties/left.js index 741f9d2a..ee594208 100644 --- a/lib/properties/left.js +++ b/lib/properties/left.js @@ -25,7 +25,7 @@ module.exports.parse = function parse(v, opt = {}) { return name; } default: { - return parsers.parseMeasurement(value); + return parsers.parseLengthPercentage(value); } } } else if (typeof value === "string") { diff --git a/lib/properties/lineHeight.js b/lib/properties/lineHeight.js index bd4dabc2..45ee09ba 100644 --- a/lib/properties/lineHeight.js +++ b/lib/properties/lineHeight.js @@ -27,7 +27,7 @@ module.exports.parse = function parse(v, opt = {}) { }); } default: { - return parsers.parseMeasurement(value, { + return parsers.parseLengthPercentage(value, { min: 0 }); } diff --git a/lib/properties/margin.js b/lib/properties/margin.js index dad882f2..feeb0365 100644 --- a/lib/properties/margin.js +++ b/lib/properties/margin.js @@ -40,7 +40,7 @@ module.exports.parse = function parse(v, opt = {}) { break; } default: { - const parsedValue = parsers.parseMeasurement([value]); + const parsedValue = parsers.parseLengthPercentage([value]); if (!parsedValue) { return; } diff --git a/lib/properties/marginBottom.js b/lib/properties/marginBottom.js index 6ca95818..5144a01d 100644 --- a/lib/properties/marginBottom.js +++ b/lib/properties/marginBottom.js @@ -27,7 +27,7 @@ module.exports.parse = function parse(v, opt = {}) { return name; } default: { - return parsers.parseMeasurement(value); + return parsers.parseLengthPercentage(value); } } } else if (typeof value === "string") { diff --git a/lib/properties/marginLeft.js b/lib/properties/marginLeft.js index 24957661..5b1cfdeb 100644 --- a/lib/properties/marginLeft.js +++ b/lib/properties/marginLeft.js @@ -27,7 +27,7 @@ module.exports.parse = function parse(v, opt = {}) { return name; } default: { - return parsers.parseMeasurement(value); + return parsers.parseLengthPercentage(value); } } } else if (typeof value === "string") { diff --git a/lib/properties/marginRight.js b/lib/properties/marginRight.js index b98825ab..5bc7d9b5 100644 --- a/lib/properties/marginRight.js +++ b/lib/properties/marginRight.js @@ -27,7 +27,7 @@ module.exports.parse = function parse(v, opt = {}) { return name; } default: { - return parsers.parseMeasurement(value); + return parsers.parseLengthPercentage(value); } } } else if (typeof value === "string") { diff --git a/lib/properties/marginTop.js b/lib/properties/marginTop.js index 0f4802e1..c63ab88b 100644 --- a/lib/properties/marginTop.js +++ b/lib/properties/marginTop.js @@ -27,7 +27,7 @@ module.exports.parse = function parse(v, opt = {}) { return name; } default: { - return parsers.parseMeasurement(value); + return parsers.parseLengthPercentage(value); } } } else if (typeof value === "string") { diff --git a/lib/properties/opacity.js b/lib/properties/opacity.js index 6a6955b2..65abbc55 100644 --- a/lib/properties/opacity.js +++ b/lib/properties/opacity.js @@ -25,7 +25,7 @@ module.exports.parse = function parse(v, opt = {}) { return parsers.parseNumber(value); } case "Percentage": { - return parsers.parsePercent(value); + return parsers.parsePercentage(value); } default: } diff --git a/lib/properties/padding.js b/lib/properties/padding.js index ef2f65d1..2ebbde64 100644 --- a/lib/properties/padding.js +++ b/lib/properties/padding.js @@ -36,7 +36,7 @@ module.exports.parse = function parse(v, opt = {}) { break; } default: { - const parsedValue = parsers.parseMeasurement([value], { + const parsedValue = parsers.parseLengthPercentage([value], { min: 0 }); if (!parsedValue) { diff --git a/lib/properties/paddingBottom.js b/lib/properties/paddingBottom.js index fe68c04f..eba939b2 100644 --- a/lib/properties/paddingBottom.js +++ b/lib/properties/paddingBottom.js @@ -26,7 +26,7 @@ module.exports.parse = function parse(v, opt = {}) { return name; } default: { - return parsers.parseMeasurement(value, { + return parsers.parseLengthPercentage(value, { min: 0 }); } diff --git a/lib/properties/paddingLeft.js b/lib/properties/paddingLeft.js index 857afe53..b7dd4ad9 100644 --- a/lib/properties/paddingLeft.js +++ b/lib/properties/paddingLeft.js @@ -26,7 +26,7 @@ module.exports.parse = function parse(v, opt = {}) { return name; } default: { - return parsers.parseMeasurement(value, { + return parsers.parseLengthPercentage(value, { min: 0 }); } diff --git a/lib/properties/paddingRight.js b/lib/properties/paddingRight.js index dc59d635..3d4bfb00 100644 --- a/lib/properties/paddingRight.js +++ b/lib/properties/paddingRight.js @@ -26,7 +26,7 @@ module.exports.parse = function parse(v, opt = {}) { return name; } default: { - return parsers.parseMeasurement(value, { + return parsers.parseLengthPercentage(value, { min: 0 }); } diff --git a/lib/properties/paddingTop.js b/lib/properties/paddingTop.js index 1343d4ca..0729e3e7 100644 --- a/lib/properties/paddingTop.js +++ b/lib/properties/paddingTop.js @@ -26,7 +26,7 @@ module.exports.parse = function parse(v, opt = {}) { return name; } default: { - return parsers.parseMeasurement(value, { + return parsers.parseLengthPercentage(value, { min: 0 }); } diff --git a/lib/properties/right.js b/lib/properties/right.js index 1ede88d9..4594eb0a 100644 --- a/lib/properties/right.js +++ b/lib/properties/right.js @@ -25,7 +25,7 @@ module.exports.parse = function parse(v, opt = {}) { return name; } default: { - return parsers.parseMeasurement(value); + return parsers.parseLengthPercentage(value); } } } else if (typeof value === "string") { diff --git a/lib/properties/top.js b/lib/properties/top.js index 42d53a53..65ab5c00 100644 --- a/lib/properties/top.js +++ b/lib/properties/top.js @@ -25,7 +25,7 @@ module.exports.parse = function parse(v, opt = {}) { return name; } default: { - return parsers.parseMeasurement(value); + return parsers.parseLengthPercentage(value); } } } else if (typeof value === "string") { diff --git a/lib/properties/width.js b/lib/properties/width.js index 5848c644..a15cf7c0 100644 --- a/lib/properties/width.js +++ b/lib/properties/width.js @@ -25,7 +25,7 @@ module.exports.parse = function parse(v, opt = {}) { return name; } default: { - return parsers.parseMeasurement(value); + return parsers.parseLengthPercentage(value); } } } else if (typeof value === "string") { diff --git a/lib/shorthandProperties.js b/lib/shorthandProperties.js deleted file mode 100644 index 61b11973..00000000 --- a/lib/shorthandProperties.js +++ /dev/null @@ -1,30 +0,0 @@ -"use strict"; - -const background = require("./properties/background"); -const border = require("./properties/border"); -const borderWidth = require("./properties/borderWidth"); -const borderStyle = require("./properties/borderStyle"); -const borderColor = require("./properties/borderColor"); -const borderTop = require("./properties/borderTop"); -const borderRight = require("./properties/borderRight"); -const borderBottom = require("./properties/borderBottom"); -const borderLeft = require("./properties/borderLeft"); -const flex = require("./properties/flex"); -const font = require("./properties/font"); - -module.exports.shorthandProperties = new Map([ - ["background", background.shorthandFor], - [ - "border", - new Map([...border.shorthandFor, ...border.positionShorthandFor, ["border-image", null]]) - ], - ["border-width", borderWidth.shorthandFor], - ["border-style", borderStyle.shorthandFor], - ["border-color", borderColor.shorthandFor], - ["border-top", borderTop.shorthandFor], - ["border-right", borderRight.shorthandFor], - ["border-bottom", borderBottom.shorthandFor], - ["border-left", borderLeft.shorthandFor], - ["flex", flex.shorthandFor], - ["font", font.shorthandFor] -]); diff --git a/lib/allWebkitProperties.js b/lib/utils/allExtraProperties.js similarity index 73% rename from lib/allWebkitProperties.js rename to lib/utils/allExtraProperties.js index 52d5d19c..a8d8711b 100644 --- a/lib/allWebkitProperties.js +++ b/lib/utils/allExtraProperties.js @@ -5,7 +5,7 @@ * current specifications or drafts, but are handled by browsers nevertheless. */ -module.exports = [ +const webkitProperties = [ "background-composite", "border-after", "border-after-color", @@ -112,3 +112,44 @@ module.exports = [ "wrap-shape-outside", "zoom" ].map((prop) => `-webkit-${prop}`); + +module.exports = new Set([ + "background-position-x", + "background-position-y", + "background-repeat-x", + "background-repeat-y", + "color-interpolation", + "color-profile", + "color-rendering", + "enable-background", + "glyph-orientation-horizontal", + "kerning", + "marker-offset", + "marks", + "pointer-events", + "shape-rendering", + "size", + "src", + "stop-color", + "stop-opacity", + "text-anchor", + "text-line-through", + "text-line-through-color", + "text-line-through-mode", + "text-line-through-style", + "text-line-through-width", + "text-overline", + "text-overline-color", + "text-overline-mode", + "text-overline-style", + "text-overline-width", + "text-rendering", + "text-underline", + "text-underline-color", + "text-underline-mode", + "text-underline-style", + "text-underline-width", + "unicode-range", + "vector-effect", + ...webkitProperties +]); diff --git a/lib/utils/normalizeBorders.js b/lib/utils/normalize.js similarity index 96% rename from lib/utils/normalizeBorders.js rename to lib/utils/normalize.js index e93c7c96..3e9662bb 100644 --- a/lib/utils/normalizeBorders.js +++ b/lib/utils/normalize.js @@ -1,11 +1,34 @@ "use strict"; const { hasVarFunc, isGlobalKeyword, isValidPropertyValue, splitValue } = require("../parsers"); +const background = require("../properties/background"); const border = require("../properties/border"); +const borderWidth = require("../properties/borderWidth"); +const borderStyle = require("../properties/borderStyle"); +const borderColor = require("../properties/borderColor"); const borderTop = require("../properties/borderTop"); const borderRight = require("../properties/borderRight"); const borderBottom = require("../properties/borderBottom"); const borderLeft = require("../properties/borderLeft"); +const flex = require("../properties/flex"); +const font = require("../properties/font"); + +module.exports.shorthandProperties = new Map([ + ["background", background.shorthandFor], + [ + "border", + new Map([...border.shorthandFor, ...border.positionShorthandFor, ["border-image", null]]) + ], + ["border-width", borderWidth.shorthandFor], + ["border-style", borderStyle.shorthandFor], + ["border-color", borderColor.shorthandFor], + ["border-top", borderTop.shorthandFor], + ["border-right", borderRight.shorthandFor], + ["border-bottom", borderBottom.shorthandFor], + ["border-left", borderLeft.shorthandFor], + ["flex", flex.shorthandFor], + ["font", font.shorthandFor] +]); module.exports.borderProperties = new Set([ "border", diff --git a/test/CSSStyleDeclaration.test.js b/test/CSSStyleDeclaration.test.js index 375222d4..b4d0ff11 100644 --- a/test/CSSStyleDeclaration.test.js +++ b/test/CSSStyleDeclaration.test.js @@ -3,9 +3,9 @@ const { describe, it } = require("node:test"); const assert = require("node:assert/strict"); const { CSSStyleDeclaration } = require("../lib/CSSStyleDeclaration"); -const allExtraProperties = require("../lib/allExtraProperties"); const allProperties = require("../lib/generated/allProperties"); const implementedProperties = require("../lib/generated/implementedProperties"); +const allExtraProperties = require("../lib/utils/allExtraProperties"); const camelize = require("../lib/utils/camelize"); describe("CSSStyleDeclaration", () => { diff --git a/test/parsers.test.js b/test/parsers.test.js index 1ebaa61a..c17e5c6e 100644 --- a/test/parsers.test.js +++ b/test/parsers.test.js @@ -207,17 +207,17 @@ describe("hasCalcFunc", () => { }); }); -describe("parseCalc", () => { +describe("resolveCalc", () => { it("should return empty string", () => { const input = ""; - const output = parsers.parseCalc(input); + const output = parsers.resolveCalc(input); assert.strictEqual(output, ""); }); it('should return "calc(6)"', () => { const input = "calc(2 * 3)"; - const output = parsers.parseCalc(input, { + const output = parsers.resolveCalc(input, { format: "specifiedValue" }); @@ -226,7 +226,7 @@ describe("parseCalc", () => { it('should return "calc(6px)"', () => { const input = "calc(2px * 3)"; - const output = parsers.parseCalc(input, { + const output = parsers.resolveCalc(input, { format: "specifiedValue" }); @@ -235,35 +235,35 @@ describe("parseCalc", () => { it('should return "rgb(calc(255/3) 0 0)"', () => { const input = "rgb(calc(255 / 3) 0 0)"; - const output = parsers.parseCalc(input); + const output = parsers.resolveCalc(input); assert.strictEqual(output, "rgb(calc(255/3) 0 0)"); }); it('should return "calc(100% - 2em)"', () => { const input = "calc(100% - 2em)"; - const output = parsers.parseCalc(input); + const output = parsers.resolveCalc(input); assert.strictEqual(output, "calc(100% - 2em)"); }); it("should return calculated value", () => { const input = "calc(2em / 3)"; - const output = parsers.parseCalc(input); + const output = parsers.resolveCalc(input); assert.strictEqual(output, "calc(0.666667em)"); }); it("should return serialized value", () => { const input = "calc(10px + 20%)"; - const output = parsers.parseCalc(input); + const output = parsers.resolveCalc(input); assert.strictEqual(output, "calc(20% + 10px)"); }); it("should return serialized value", () => { const input = "calc(100vh + 10px)"; - const output = parsers.parseCalc(input); + const output = parsers.resolveCalc(input); assert.strictEqual(output, "calc(10px + 100vh)"); }); @@ -526,17 +526,17 @@ describe("parseLength", () => { }); }); -describe("parsePercent", () => { +describe("parsePercentage", () => { it("should return undefined", () => { const input = ""; - const output = parsers.parsePercent(input); + const output = parsers.parsePercentage(input); assert.strictEqual(output, undefined); }); it("should return undefined", () => { const input = []; - const output = parsers.parsePercent(input); + const output = parsers.parsePercentage(input); assert.strictEqual(output, undefined); }); @@ -549,7 +549,7 @@ describe("parsePercent", () => { value: "100" } ]; - const output = parsers.parsePercent(input); + const output = parsers.parsePercentage(input); assert.strictEqual(output, undefined); }); @@ -561,7 +561,7 @@ describe("parsePercent", () => { value: "100" } ]; - const output = parsers.parsePercent(input); + const output = parsers.parsePercentage(input); assert.strictEqual(output, undefined); }); @@ -573,7 +573,7 @@ describe("parsePercent", () => { value: "10" } ]; - const output = parsers.parsePercent(input); + const output = parsers.parsePercentage(input); assert.strictEqual(output, "10%"); }); @@ -585,7 +585,7 @@ describe("parsePercent", () => { value: "-50" } ]; - const output = parsers.parsePercent(input, { + const output = parsers.parsePercentage(input, { min: 0, max: 100 }); @@ -600,7 +600,7 @@ describe("parsePercent", () => { value: "150" } ]; - const output = parsers.parsePercent(input, { + const output = parsers.parsePercentage(input, { min: 0, max: 100 }); @@ -615,7 +615,7 @@ describe("parsePercent", () => { value: "-50" } ]; - const output = parsers.parsePercent(input, { + const output = parsers.parsePercentage(input, { min: 0, max: 100, clamp: true @@ -631,7 +631,7 @@ describe("parsePercent", () => { value: "150" } ]; - const output = parsers.parsePercent(input, { + const output = parsers.parsePercentage(input, { min: 0, max: 100, clamp: true @@ -641,17 +641,17 @@ describe("parsePercent", () => { }); }); -describe("parseMeasurement", () => { +describe("parseLengthPercentage", () => { it("should return undefined", () => { const input = ""; - const output = parsers.parseMeasurement(input); + const output = parsers.parseLengthPercentage(input); assert.strictEqual(output, undefined); }); it("should return undefined", () => { const input = []; - const output = parsers.parseMeasurement(input); + const output = parsers.parseLengthPercentage(input); assert.strictEqual(output, undefined); }); @@ -663,7 +663,7 @@ describe("parseMeasurement", () => { value: "100" } ]; - const output = parsers.parseMeasurement(input); + const output = parsers.parseLengthPercentage(input); assert.strictEqual(output, undefined); }); @@ -675,7 +675,7 @@ describe("parseMeasurement", () => { value: "100" } ]; - const output = parsers.parseMeasurement(input); + const output = parsers.parseLengthPercentage(input); assert.strictEqual(output, undefined); }); @@ -688,7 +688,7 @@ describe("parseMeasurement", () => { value: "180" } ]; - const output = parsers.parseMeasurement(input); + const output = parsers.parseLengthPercentage(input); assert.strictEqual(output, undefined); }); @@ -701,7 +701,7 @@ describe("parseMeasurement", () => { value: "1" } ]; - const output = parsers.parseMeasurement(input); + const output = parsers.parseLengthPercentage(input); assert.strictEqual(output, "1em"); }); @@ -713,7 +713,7 @@ describe("parseMeasurement", () => { value: "100" } ]; - const output = parsers.parseMeasurement(input); + const output = parsers.parseLengthPercentage(input); assert.strictEqual(output, "100%"); }); @@ -725,7 +725,7 @@ describe("parseMeasurement", () => { value: "0" } ]; - const output = parsers.parseMeasurement(input); + const output = parsers.parseLengthPercentage(input); assert.strictEqual(output, "0px"); }); @@ -738,7 +738,7 @@ describe("parseMeasurement", () => { value: "-1" } ]; - const output = parsers.parseMeasurement(input, { + const output = parsers.parseLengthPercentage(input, { min: 0, max: 1 }); @@ -754,7 +754,7 @@ describe("parseMeasurement", () => { value: "1.1" } ]; - const output = parsers.parseMeasurement(input, { + const output = parsers.parseLengthPercentage(input, { min: 0, max: 1 }); @@ -770,7 +770,7 @@ describe("parseMeasurement", () => { value: "-1" } ]; - const output = parsers.parseMeasurement(input, { + const output = parsers.parseLengthPercentage(input, { min: 0, max: 1, clamp: true @@ -787,7 +787,7 @@ describe("parseMeasurement", () => { value: "1.1" } ]; - const output = parsers.parseMeasurement(input, { + const output = parsers.parseLengthPercentage(input, { min: 0, max: 1, clamp: true @@ -1331,17 +1331,17 @@ describe("parseColor", () => { }); }); -describe("parseImage", () => { +describe("parseGradient", () => { it("should return undefined", () => { const input = ""; - const output = parsers.parseImage(input); + const output = parsers.parseGradient(input); assert.strictEqual(output, undefined); }); it("should return undefined", () => { const input = []; - const output = parsers.parseImage(input); + const output = parsers.parseGradient(input); assert.strictEqual(output, undefined); }); @@ -1353,7 +1353,7 @@ describe("parseImage", () => { name: "none" } ]; - const output = parsers.parseImage(input); + const output = parsers.parseGradient(input); assert.strictEqual(output, undefined); }); @@ -1366,7 +1366,7 @@ describe("parseImage", () => { value: "circle -10px at center, red, blue" } ]; - const output = parsers.parseImage(input); + const output = parsers.parseGradient(input); assert.strictEqual(output, undefined); }); @@ -1379,7 +1379,7 @@ describe("parseImage", () => { value: "green, blue" } ]; - const output = parsers.parseImage(input); + const output = parsers.parseGradient(input); assert.strictEqual(output, "linear-gradient(green, blue)"); }); From d399c7a4bcfbfd9fc821e1bc8fe6d51fa7f65f88 Mon Sep 17 00:00:00 2001 From: "asamuzaK (Kazz)" Date: Mon, 25 Aug 2025 01:09:38 +0900 Subject: [PATCH 41/41] Update normalize.js --- lib/CSSStyleDeclaration.js | 298 ++++++++++--------------- lib/{utils => }/normalize.js | 363 +++++++++++++++++++++++++------ lib/properties/margin.js | 35 ++- lib/properties/marginBottom.js | 11 +- lib/properties/marginLeft.js | 9 +- lib/properties/marginRight.js | 11 +- lib/properties/marginTop.js | 11 +- lib/properties/padding.js | 35 ++- lib/properties/paddingBottom.js | 11 +- lib/properties/paddingLeft.js | 11 +- lib/properties/paddingRight.js | 11 +- lib/properties/paddingTop.js | 11 +- test/CSSStyleDeclaration.test.js | 24 +- 13 files changed, 538 insertions(+), 303 deletions(-) rename lib/{utils => }/normalize.js (69%) diff --git a/lib/CSSStyleDeclaration.js b/lib/CSSStyleDeclaration.js index 8b3bce51..1f71d111 100644 --- a/lib/CSSStyleDeclaration.js +++ b/lib/CSSStyleDeclaration.js @@ -7,23 +7,24 @@ const allProperties = require("./generated/allProperties"); const implementedProperties = require("./generated/implementedProperties"); const generatedProperties = require("./generated/properties"); +const { + borderProperties, + getPositionValue, + normalizeProperties, + prepareBorderProperties, + prepareProperties, + shorthandProperties +} = require("./normalize"); const { hasVarFunc, isValidPropertyValue, parseCSS, parsePropertyValue, parseShorthand, - prepareValue, - splitValue + prepareValue } = require("./parsers"); const allExtraProperties = require("./utils/allExtraProperties"); const { dashedToCamelCase } = require("./utils/camelize"); -const { - borderProperties, - normalizeBorderProperties, - prepareBorderProperties, - shorthandProperties -} = require("./utils/normalize"); const { getPropertyDescriptor } = require("./utils/propertyDescriptors"); const { asciiLowercase } = require("./utils/strings"); @@ -139,22 +140,18 @@ class CSSStyleDeclaration { for (let i = 0; i < this._length; i++) { const property = this[i]; const value = this.getPropertyValue(property); - const priority = this._priorities.get(property); - if (priority === "important") { - properties.set(property, { property, value, priority }); - } else { - if (shorthandProperties.has(property)) { - const longhandProperties = shorthandProperties.get(property); - for (const [longhand] of longhandProperties) { - if (properties.has(longhand) && !this._priorities.get(longhand)) { - properties.delete(longhand); - } + const priority = this._priorities.get(property) ?? ""; + if (shorthandProperties.has(property)) { + const { shorthandFor } = shorthandProperties.get(property); + for (const [longhand] of shorthandFor) { + if (priority || !this._priorities.get(longhand)) { + properties.delete(longhand); } } - properties.set(property, { property, value, priority: null }); } + properties.set(property, { property, value, priority }); } - const normalizedProperties = normalizeBorderProperties(properties); + const normalizedProperties = normalizeProperties(properties); const parts = []; for (const { property, value, priority } of normalizedProperties.values()) { if (priority) { @@ -189,6 +186,7 @@ class CSSStyleDeclaration { true ); if (valueObj?.children) { + const properties = new Map(); for (const item of valueObj.children) { const { important, @@ -198,18 +196,39 @@ class CSSStyleDeclaration { const priority = important ? "important" : ""; const isCustomProperty = property.startsWith("--"); if (isCustomProperty || hasVarFunc(value)) { - this.setProperty(property, value, priority); + if (properties.has(property)) { + const { priority: itemPriority } = properties.get(property); + if (!itemPriority) { + properties.set(property, { property, value, priority }); + } + } else { + properties.set(property, { property, value, priority }); + } } else { const parsedValue = parsePropertyValue(property, value, { globalObject: this._global }); if (parsedValue) { - this.setProperty(property, parsedValue, priority); + if (properties.has(property)) { + const { priority: itemPriority } = properties.get(property); + if (!itemPriority) { + properties.set(property, { property, value, priority }); + } + } else { + properties.set(property, { property, value, priority }); + } } else { this.removeProperty(property); } } } + const parsedProperties = prepareProperties(properties, { + globalObject: this._global + }); + for (const [property, item] of parsedProperties) { + const { priority, value } = item; + this.setProperty(property, value, priority); + } } } catch { return; @@ -308,9 +327,9 @@ class CSSStyleDeclaration { /** * @param {string} prop * @param {string} val - * @param {string?} [priority] - "important" or null + * @param {string} prior */ - setProperty(prop, val, priority = null) { + setProperty(prop, val, prior) { if (this._readonly) { const msg = `Property ${prop} can not be modified.`; const name = "NoModificationAllowedError"; @@ -322,6 +341,7 @@ class CSSStyleDeclaration { this.removeProperty(prop); return; } + const priority = prior === "important" ? "important" : ""; const isCustomProperty = prop.startsWith("--"); if (isCustomProperty) { this._setProperty(prop, value, priority); @@ -331,12 +351,12 @@ class CSSStyleDeclaration { if (!allProperties.has(property) && !allExtraProperties.has(property)) { return; } - this[property] = value; if (priority) { this._priorities.set(property, priority); } else { this._priorities.delete(property); } + this[property] = value; } } @@ -386,60 +406,13 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { enumerable: false }, - _implicitGetter: { - /** - * @param {string} prefix - * @param {string} part - * @param {Array.} positions - */ - value(prefix, part, positions = []) { - const suffix = part ? `-${part}` : ""; - const values = []; - for (const position of positions) { - const val = this.getPropertyValue(`${prefix}-${position}${suffix}`); - if (val === "" || hasVarFunc(val)) { - return ""; - } - values.push(val); - } - if (!values.length) { - return ""; - } - switch (positions.length) { - case 4: { - const [top, right, bottom, left] = values; - if (top === right && top === bottom && right === left) { - return top; - } - if (top !== right && top === bottom && right === left) { - return `${top} ${right}`; - } - if (top !== right && top !== bottom && right === left) { - return `${top} ${right} ${bottom}`; - } - return `${top} ${right} ${bottom} ${left}`; - } - case 2: { - const [x, y] = values; - if (x === y) { - return x; - } - return `${x} ${y}`; - } - default: - return ""; - } - }, - enumerable: false - }, - _setProperty: { /** * @param {string} property * @param {string} val - * @param {string?} [priority] + * @param {string} priority */ - value(property, val, priority = null) { + value(property, val, priority) { if (typeof val !== "string") { return; } @@ -463,12 +436,12 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { this[this._length] = property; this._length++; } - this._values.set(property, val); - if (priority) { + if (priority === "important") { this._priorities.set(property, priority); } else { this._priorities.delete(property); } + this._values.set(property, val); if ( typeof this._onChange === "function" && this.cssText !== originalText && @@ -526,141 +499,100 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { enumerable: false }, - _implicitSetter: { + _implicitShorthandSetter: { /** - * @param {string} prefix - * @param {string} part - * @param {string|Array.} val - * @param {Function} parser - * @param {Array.} positions + * @param {string} prop + * @param {Array|string} val */ - value(prefix, part, val, parser, positions = []) { - const suffix = part ? `-${part}` : ""; - const values = []; - if (val === "") { - values.push(val); - } else if (Array.isArray(val) && val.length) { - values.push(...val); - } else { - const parsedValue = parser(val, { - globalObject: this._global - }); - if (typeof parsedValue !== "string") { - return; - } - values.push(...splitValue(parsedValue)); + value(prop, val) { + if (!shorthandProperties.has(prop)) { + return; } - if (!values.length || values.length > positions.length) { + const shorthandValues = []; + if (Array.isArray(val)) { + shorthandValues.push(...val); + } else if (typeof val === "string") { + shorthandValues.push(val); + } else { return; } - const shorthandProp = `${prefix}${suffix}`; - const shorthandVal = values.join(" "); - const positionValues = [...values]; - switch (positions.length) { - case 4: { - if (values.length === 1) { - positionValues.push(values[0], values[0], values[0]); - } else if (values.length === 2) { - positionValues.push(values[0], values[1]); - } else if (values.length === 3) { - positionValues.push(values[1]); - } - break; - } - case 2: { - if (values.length === 1) { - positionValues.push(values[0]); - } - break; + const priority = this._priorities.get(prop) ?? ""; + const { shorthandFor } = shorthandProperties.get(prop); + let hasPriority = false; + for (const [longhandProperty, { position }] of shorthandFor) { + const longhandValue = getPositionValue(shorthandValues, position); + const longhandPriority = this._priorities.get(longhandProperty) ?? ""; + if (priority) { + this._setProperty(longhandProperty, longhandValue, longhandPriority); + } else if (longhandPriority) { + hasPriority = true; + } else { + this._setProperty(longhandProperty, longhandValue, longhandPriority); } - default: } - const longhandValues = []; - for (const position of positions) { - const property = `${prefix}-${position}${suffix}`; - const longhandValue = this.getPropertyValue(property); - const longhandPriority = this._priorities.get(property); - longhandValues.push([longhandValue, longhandPriority]); - } - for (let i = 0; i < positions.length; i++) { - const property = `${prefix}-${positions[i]}${suffix}`; - const [longhandValue, longhandPriority] = longhandValues[i]; - const longhandVal = longhandPriority ? longhandValue : positionValues[i]; - this.removeProperty(property); - this._values.set(property, longhandVal); + if (hasPriority) { + this.removeProperty(prop); + } else { + this._setProperty(prop, shorthandValues.join(" "), priority); } - this._setProperty(shorthandProp, shorthandVal); - }, - enumerable: false + } }, - // Companion to implicitSetter, but for the individual parts. - // This sets the individual value, and checks to see if all sub-parts are - // set. If so, it sets the shorthand version and removes the individual parts - // from the cssText. - _subImplicitSetter: { + _implicitLonghandSetter: { /** - * @param {string} prefix - * @param {string} part + * @param {string} prop * @param {string} val - * @param {Function} parser - * @param {Array.} positions */ - value(prefix, part, val, parser, positions = []) { - const parsedValue = parser(val, { - globalObject: this._global - }); - if (typeof parsedValue !== "string") { + value(prop, val) { + const { logicalPropertyGroup: shorthandProperty } = implementedProperties.get(prop) ?? {}; + if (!shorthandProperty || !shorthandProperties.has(shorthandProperty)) { return; } - const property = `${prefix}-${part}`; - this._setProperty(property, parsedValue); - const combinedPriority = this._priorities.get(prefix); - const subparts = []; - for (const position of positions) { - subparts.push(`${prefix}-${position}`); - } - const parts = subparts.map((subpart) => this._values.get(subpart)); - const priorities = subparts.map((subpart) => this._priorities.get(subpart)); - const [priority] = priorities; - // Combine into a single property if all values are set and have the same - // priority. - if ( - priority === combinedPriority && - parts.every((p) => p) && - priorities.every((p) => p === priority) - ) { - for (let i = 0; i < subparts.length; i++) { - this.removeProperty(subparts[i]); - this._values.set(subparts[i], parts[i]); - } - this._setProperty(prefix, parts.join(" "), priority); + const shorthandPriority = this._priorities.get(shorthandProperty); + const priority = this._priorities.get(prop) ?? ""; + this._setProperty(shorthandProperty, ""); + if (shorthandPriority && priority) { + this._setProperty(prop, val); } else { - this.removeProperty(prefix); - for (let i = 0; i < subparts.length; i++) { - // The property we're setting won't be important, the rest will either - // keep their priority or inherit it from the combined property - const subPriority = subparts[i] === property ? "" : priorities[i] || combinedPriority; - this._setProperty(subparts[i], parts[i], subPriority); + this._setProperty(prop, val, priority); + } + if (val && !hasVarFunc(val)) { + const longhandValues = []; + const { shorthandFor, position: shorthandPosition } = + shorthandProperties.get(shorthandProperty); + for (const [longhandProperty] of shorthandFor) { + const longhandValue = this.getPropertyValue(longhandProperty); + const longhandPriority = this._priorities.get(longhandProperty) ?? ""; + if (!longhandValue || longhandPriority !== priority) { + return; + } + longhandValues.push(longhandValue); + } + if (longhandValues.length === shorthandFor.size) { + const replacedValue = getPositionValue(longhandValues, shorthandPosition); + this._setProperty(shorthandProperty, replacedValue); } } - }, - enumerable: false + } }, _implicitBorderSetter: { /** * @param {string} prop - * @param {string} val + * @param {object|Array|string} val */ value(prop, val) { const properties = new Map(); - if (prop !== "border") { + if (prop === "border") { + const priority = this._priorities.get(prop) ?? ""; + properties.set(prop, { propery: prop, value: val, priority }); + } else { for (let i = 0; i < this._length; i++) { const property = this[i]; if (borderProperties.has(property)) { const value = this.getPropertyValue(property); - properties.set(property, { property, value, priority: null }); + const priority = this._priorities.get(property) ?? ""; + properties.set(property, { property, value, priority }); } } } @@ -668,8 +600,8 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { globalObject: this._global }); for (const [property, item] of parsedProperties) { - const { value } = item; - this._setProperty(property, value); + const { priority, value } = item; + this._setProperty(property, value, priority); } }, enumerable: false diff --git a/lib/utils/normalize.js b/lib/normalize.js similarity index 69% rename from lib/utils/normalize.js rename to lib/normalize.js index 3e9662bb..d96a6b4d 100644 --- a/lib/utils/normalize.js +++ b/lib/normalize.js @@ -1,33 +1,46 @@ "use strict"; -const { hasVarFunc, isGlobalKeyword, isValidPropertyValue, splitValue } = require("../parsers"); -const background = require("../properties/background"); -const border = require("../properties/border"); -const borderWidth = require("../properties/borderWidth"); -const borderStyle = require("../properties/borderStyle"); -const borderColor = require("../properties/borderColor"); -const borderTop = require("../properties/borderTop"); -const borderRight = require("../properties/borderRight"); -const borderBottom = require("../properties/borderBottom"); -const borderLeft = require("../properties/borderLeft"); -const flex = require("../properties/flex"); -const font = require("../properties/font"); +const implementedProperties = require("./generated/implementedProperties"); +const { hasVarFunc, isGlobalKeyword, isValidPropertyValue, splitValue } = require("./parsers"); +const background = require("./properties/background"); +const border = require("./properties/border"); +const borderWidth = require("./properties/borderWidth"); +const borderStyle = require("./properties/borderStyle"); +const borderColor = require("./properties/borderColor"); +const borderTop = require("./properties/borderTop"); +const borderRight = require("./properties/borderRight"); +const borderBottom = require("./properties/borderBottom"); +const borderLeft = require("./properties/borderLeft"); +const flex = require("./properties/flex"); +const font = require("./properties/font"); +const margin = require("./properties/margin"); +const padding = require("./properties/padding"); module.exports.shorthandProperties = new Map([ - ["background", background.shorthandFor], + ["background", background], [ "border", - new Map([...border.shorthandFor, ...border.positionShorthandFor, ["border-image", null]]) + { + definition: border.definition, + parse: border.parse, + shorthandFor: new Map([ + ...border.shorthandFor, + ...border.positionShorthandFor, + ["border-image", null] + ]) + } ], - ["border-width", borderWidth.shorthandFor], - ["border-style", borderStyle.shorthandFor], - ["border-color", borderColor.shorthandFor], - ["border-top", borderTop.shorthandFor], - ["border-right", borderRight.shorthandFor], - ["border-bottom", borderBottom.shorthandFor], - ["border-left", borderLeft.shorthandFor], - ["flex", flex.shorthandFor], - ["font", font.shorthandFor] + ["border-width", borderWidth], + ["border-style", borderStyle], + ["border-color", borderColor], + ["border-top", borderTop], + ["border-right", borderRight], + ["border-bottom", borderBottom], + ["border-left", borderLeft], + ["flex", flex], + ["font", font], + ["margin", margin], + ["padding", padding] ]); module.exports.borderProperties = new Set([ @@ -46,14 +59,14 @@ const borderElements = { positions: ["top", "right", "bottom", "left"], lines: ["width", "style", "color"] }; -const firstInitialKey = border.initialValues.keys().next().value; -const firstInitialValue = border.initialValues.get(firstInitialKey); +const borderFirstInitialKey = border.initialValues.keys().next().value; +const borderFirstInitialValue = border.initialValues.get(borderFirstInitialKey); const getPropertyItem = (property, properties) => { const propertyItem = properties.get(property) ?? { property, value: "", - priority: null + priority: "" }; return propertyItem; }; @@ -79,7 +92,7 @@ const replaceShorthandValue = (value, shorthandValue, opt = {}) => { globalObject }) : { - [firstInitialKey]: firstInitialValue + [borderFirstInitialKey]: borderFirstInitialValue }; const keys = border.shorthandFor.keys(); for (const key of keys) { @@ -89,7 +102,7 @@ const replaceShorthandValue = (value, shorthandValue, opt = {}) => { parsedValue = valueObj[key]; } if (parsedValue === initialValue) { - if (key === firstInitialKey) { + if (key === borderFirstInitialKey) { if (!Object.hasOwn(shorthandObj, key)) { shorthandObj[key] = parsedValue; } @@ -98,21 +111,115 @@ const replaceShorthandValue = (value, shorthandValue, opt = {}) => { } } else { shorthandObj[key] = parsedValue; - if (shorthandObj[firstInitialKey] && shorthandObj[firstInitialKey] === firstInitialValue) { - delete shorthandObj[firstInitialKey]; + if ( + shorthandObj[borderFirstInitialKey] && + shorthandObj[borderFirstInitialKey] === borderFirstInitialValue + ) { + delete shorthandObj[borderFirstInitialKey]; } } } return Object.values(shorthandObj).join(" "); }; -const replaceLineValue = (value, lineValue, position) => { - const values = splitValue(lineValue); - switch (values.length) { +module.exports.getPositionValue = function getPositionValue(positionValues, position) { + switch (positionValues.length) { case 1: { - const [val1] = values; + const [val1] = positionValues; + return val1; + } + case 2: { + const [val1, val2] = positionValues; + switch (position) { + case "top": { + return val1; + } + case "right": { + return val2; + } + case "bottom": { + return val1; + } + case "left": { + return val2; + } + default: { + if (val1 === val2) { + return val1; + } + return `${val1} ${val2}`; + } + } + } + case 3: { + const [val1, val2, val3] = positionValues; + switch (position) { + case "top": { + return val1; + } + case "right": { + return val2; + } + case "bottom": { + return val3; + } + case "left": { + return val2; + } + default: { + if (val1 === val3) { + if (val1 === val2) { + return val1; + } + return `${val1} ${val2}`; + } + return `${val1} ${val2} ${val3}`; + } + } + } + case 4: { + const [val1, val2, val3, val4] = positionValues; + switch (position) { + case "top": { + return val1; + } + case "right": { + return val2; + } + case "bottom": { + return val3; + } + case "left": { + return val4; + } + default: { + if (val2 === val4) { + if (val1 === val3) { + if (val1 === val2) { + return val1; + } + return `${val1} ${val2}`; + } + return `${val1} ${val2} ${val3}`; + } + return `${val1} ${val2} ${val3} ${val4}`; + } + } + } + default: + } +}; + +module.exports.replacePositionValue = function replacePositionValue( + value, + positionValues, + position +) { + switch (positionValues.length) { + case 1: { + const [val1] = positionValues; if (val1 === value) { - return lineValue; + return positionValues.join(" "); } switch (position) { case "top": { @@ -132,29 +239,32 @@ const replaceLineValue = (value, lineValue, position) => { break; } case 2: { - const [val1, val2] = values; + const [val1, val2] = positionValues; + if (val1 === val2) { + return module.exports.replacePositionValue(value, [val1], position); + } switch (position) { case "top": { if (val1 === value) { - return lineValue; + return positionValues.join(" "); } return [value, val2, val1].join(" "); } case "right": { if (val2 === value) { - return lineValue; + return positionValues.join(" "); } return [val1, value, val1, val2].join(" "); } case "bottom": { if (val1 === value) { - return lineValue; + return positionValues.join(" "); } return [val1, val2, value].join(" "); } case "left": { if (val2 === value) { - return lineValue; + return positionValues.join(" "); } return [val1, val2, val1, value].join(" "); } @@ -163,11 +273,14 @@ const replaceLineValue = (value, lineValue, position) => { break; } case 3: { - const [val1, val2, val3] = values; + const [val1, val2, val3] = positionValues; + if (val1 === val3) { + return module.exports.replacePositionValue(value, [val1, val2], position); + } switch (position) { case "top": { if (val1 === value) { - return lineValue; + return positionValues.join(" "); } else if (val3 === value) { return [value, val2].join(" "); } @@ -175,13 +288,13 @@ const replaceLineValue = (value, lineValue, position) => { } case "right": { if (val2 === value) { - return lineValue; + return positionValues.join(" "); } return [val1, value, val3, val2].join(" "); } case "bottom": { if (val3 === value) { - return lineValue; + return positionValues.join(" "); } else if (val1 === value) { return [val1, val2].join(" "); } @@ -189,7 +302,7 @@ const replaceLineValue = (value, lineValue, position) => { } case "left": { if (val2 === value) { - return lineValue; + return positionValues.join(" "); } return [val1, val2, val3, value].join(" "); } @@ -198,17 +311,20 @@ const replaceLineValue = (value, lineValue, position) => { break; } case 4: { - const [val1, val2, val3, val4] = values; + const [val1, val2, val3, val4] = positionValues; + if (val2 === val4) { + return module.exports.replacePositionValue(value, [val1, val2, val3], position); + } switch (position) { case "top": { if (val1 === value) { - return lineValue; + return positionValues.join(" "); } return [value, val2, val3, val4].join(" "); } case "right": { if (val2 === value) { - return lineValue; + return positionValues.join(" "); } else if (val4 === value) { return [val1, value, val3].join(" "); } @@ -216,13 +332,13 @@ const replaceLineValue = (value, lineValue, position) => { } case "bottom": { if (val3 === value) { - return lineValue; + return positionValues.join(" "); } return [val1, val2, value, val4].join(" "); } case "left": { if (val4 === value) { - return lineValue; + return positionValues.join(" "); } else if (val2 === value) { return [val1, val2, val3].join(" "); } @@ -302,7 +418,11 @@ module.exports.prepareBorderProperties = function prepareBorderProperties( nameItem.value = ""; } if (lineItem.value) { - lineItem.value = replaceLineValue(propertyValue, lineItem.value, prop2); + lineItem.value = module.exports.replacePositionValue( + propertyValue, + splitValue(lineItem.value), + prop2 + ); } if ( positionItem.value && @@ -417,11 +537,7 @@ module.exports.prepareBorderProperties = function prepareBorderProperties( borderItems.set(lineProperty, lineItem); // border shorthand } else { - const nameItem = { - property, - value, - priority: null - }; + const nameItem = getPropertyItem(nameProperty, properties); const imageItem = getPropertyItem(imageProperty, properties); const propertyValue = hasVarFunc(value) ? "" : value; imageItem.value = propertyValue ? "none" : ""; @@ -539,9 +655,9 @@ module.exports.prepareBorderProperties = function prepareBorderProperties( const positionProperty = `${prop1}-${prop2}`; const positionItem = getPropertyItem(positionProperty, properties); if (nameItem.value) { - for (const lineValue of Object.values(value)) { + for (const positionValue of Object.values(value)) { if ( - !matchesShorthandValue(property, lineValue, nameItem.value, { + !matchesShorthandValue(property, positionValue, nameItem.value, { globalObject }) ) { @@ -558,15 +674,27 @@ module.exports.prepareBorderProperties = function prepareBorderProperties( const itemValue = value[longhandProperty]; if (line === "width") { if (lineWidthItem.value) { - lineWidthItem.value = replaceLineValue(itemValue, lineWidthItem.value, prop2); + lineWidthItem.value = module.exports.replacePositionValue( + itemValue, + splitValue(lineWidthItem.value), + prop2 + ); } } else if (line === "style") { if (lineStyleItem.value) { - lineStyleItem.value = replaceLineValue(itemValue, lineStyleItem.value, prop2); + lineStyleItem.value = module.exports.replacePositionValue( + itemValue, + splitValue(lineStyleItem.value), + prop2 + ); } } else if (line === "color") { if (lineColorItem.value) { - lineColorItem.value = replaceLineValue(itemValue, lineColorItem.value, prop2); + lineColorItem.value = module.exports.replacePositionValue( + itemValue, + splitValue(lineColorItem.value), + prop2 + ); } } longhandItem.value = itemValue; @@ -574,15 +702,27 @@ module.exports.prepareBorderProperties = function prepareBorderProperties( const itemValue = border.initialValues.get(`${prop1}-${line}`); if (line === "width") { if (lineWidthItem.value) { - lineWidthItem.value = replaceLineValue(itemValue, lineWidthItem.value, prop2); + lineWidthItem.value = module.exports.replacePositionValue( + itemValue, + splitValue(lineWidthItem.value), + prop2 + ); } } else if (line === "style") { if (lineStyleItem.value) { - lineStyleItem.value = replaceLineValue(itemValue, lineStyleItem.value, prop2); + lineStyleItem.value = module.exports.replacePositionValue( + itemValue, + splitValue(lineStyleItem.value), + prop2 + ); } } else if (line === "color") { if (lineColorItem.value) { - lineColorItem.value = replaceLineValue(itemValue, lineColorItem.value, prop2); + lineColorItem.value = module.exports.replacePositionValue( + itemValue, + splitValue(lineColorItem.value), + prop2 + ); } } longhandItem.value = itemValue; @@ -659,7 +799,7 @@ module.exports.prepareBorderProperties = function prepareBorderProperties( properties.get(lineProperty) ?? { property: lineProperty, value: "", - priority: null + priority: "" }; borderProperties.set(lineProperty, lineItem); } @@ -669,7 +809,7 @@ module.exports.prepareBorderProperties = function prepareBorderProperties( properties.get(positionProperty) ?? { property: positionProperty, value: "", - priority: null + priority: "" }; borderProperties.set(positionProperty, positionItem); for (const line of lines) { @@ -678,7 +818,7 @@ module.exports.prepareBorderProperties = function prepareBorderProperties( properties.get(longhandProperty) ?? { property: longhandProperty, value: "", - priority: null + priority: "" }; borderProperties.set(longhandProperty, longhandItem); } @@ -686,13 +826,93 @@ module.exports.prepareBorderProperties = function prepareBorderProperties( const borderImageItem = borderItems.get(imageProperty) ?? { property: imageProperty, value: "", - priority: null + priority: "" }; borderProperties.set(imageProperty, borderImageItem); return borderProperties; }; -module.exports.normalizeBorderProperties = function (properties) { +module.exports.prepareProperties = function (properties) { + const parsedProperties = new Map(); + const createShorthands = new Map(); + for (const [property, item] of properties) { + const { value, priority } = item; + const { logicalPropertyGroup: shorthandProperty } = implementedProperties.get(property) ?? {}; + if (module.exports.shorthandProperties.has(shorthandProperty)) { + if (!createShorthands.has(shorthandProperty)) { + createShorthands.set(shorthandProperty, new Map()); + } + const longhandItems = createShorthands.get(shorthandProperty); + if (longhandItems.size) { + const firstPropertyKey = longhandItems.keys().next().value; + const { priority: firstPropertyPriority } = longhandItems.get(firstPropertyKey); + if (priority === firstPropertyPriority) { + longhandItems.set(property, { property, value, priority }); + createShorthands.set(shorthandProperty, longhandItems); + } else { + parsedProperties.delete(shorthandProperty); + } + } else { + longhandItems.set(property, { property, value, priority }); + createShorthands.set(shorthandProperty, longhandItems); + } + parsedProperties.set(property, item); + } else if (module.exports.shorthandProperties.has(property)) { + const shorthandItem = module.exports.shorthandProperties.get(property); + const parsedValue = shorthandItem.parse(value); + let omitShorthandProperty = false; + if (Array.isArray(parsedValue)) { + for (const [longhandProperty, longhandItem] of shorthandItem.shorthandFor) { + if (!priority && properties.has(longhandProperty)) { + const { priority: longhandPriority } = properties.get(longhandProperty); + if (longhandPriority) { + omitShorthandProperty = true; + continue; + } + } + const { position } = longhandItem; + const longhandValue = module.exports.getPositionValue(parsedValue, position); + parsedProperties.set(longhandProperty, { + property: longhandProperty, + value: longhandValue, + priority + }); + } + } + if (!omitShorthandProperty) { + parsedProperties.set(property, item); + } + } else { + parsedProperties.set(property, item); + } + } + if (createShorthands.size) { + for (const [property, item] of createShorthands) { + const shorthandItem = module.exports.shorthandProperties.get(property); + if (item.size === shorthandItem.shorthandFor.size) { + if (shorthandItem.position) { + const positionValues = []; + let priority = ""; + for (const { value: longhandValue, priority: longhandPriority } of item.values()) { + positionValues.push(longhandValue); + if (longhandPriority) { + priority = longhandPriority; + } + } + const value = module.exports.getPositionValue(positionValues, shorthandItem.position); + parsedProperties.set(property, { + property, + value, + priority + }); + } + } + } + } + return parsedProperties; +}; + +const normalizeBorderProperties = (properties) => { const { name, positions, lines } = borderElements; if (properties.has(name)) { for (const line of lines) { @@ -736,3 +956,8 @@ module.exports.normalizeBorderProperties = function (properties) { } return properties; }; + +module.exports.normalizeProperties = function (properties) { + normalizeBorderProperties(properties); + return properties; +}; diff --git a/lib/properties/margin.js b/lib/properties/margin.js index feeb0365..53d209ae 100644 --- a/lib/properties/margin.js +++ b/lib/properties/margin.js @@ -1,8 +1,19 @@ "use strict"; const parsers = require("../parsers"); +const marginTop = require("./marginTop"); +const marginRight = require("./marginRight"); +const marginBottom = require("./marginBottom"); +const marginLeft = require("./marginLeft"); -const positions = ["top", "right", "bottom", "left"]; +module.exports.position = "all"; + +module.exports.shorthandFor = new Map([ + ["margin-top", marginTop], + ["margin-right", marginRight], + ["margin-bottom", marginBottom], + ["margin-left", marginLeft] +]); module.exports.parse = function parse(v, opt = {}) { const { globalObject } = opt; @@ -52,7 +63,7 @@ module.exports.parse = function parse(v, opt = {}) { parsedValues.push(values); } if (parsedValues.length) { - return parsedValues.join(" "); + return parsedValues; } }; @@ -60,21 +71,21 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._implicitSetter("margin", "", "", module.exports.parse, positions); + for (const [longhand] of module.exports.shorthandFor) { + this._setProperty(longhand, ""); + } this._setProperty("margin", v); } else { - this._implicitSetter("margin", "", v, module.exports.parse, positions); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (Array.isArray(val) || typeof val === "string") { + this._implicitShorthandSetter("margin", val); + } } }, get() { - const val = this._implicitGetter("margin", "", positions); - if (val === "") { - return this.getPropertyValue("margin"); - } - if (parsers.hasVarFunc(val)) { - return ""; - } - return val; + return this.getPropertyValue("margin"); }, enumerable: true, configurable: true diff --git a/lib/properties/marginBottom.js b/lib/properties/marginBottom.js index 5144a01d..ce0d097d 100644 --- a/lib/properties/marginBottom.js +++ b/lib/properties/marginBottom.js @@ -2,7 +2,7 @@ const parsers = require("../parsers"); -const positions = ["top", "right", "bottom", "left"]; +module.exports.position = "bottom"; module.exports.parse = function parse(v, opt = {}) { const { globalObject } = opt; @@ -39,10 +39,15 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._setProperty("margin", ""); this._setProperty("margin-bottom", v); + this._setProperty("margin", ""); } else { - this._subImplicitSetter("margin", "bottom", v, module.exports.parse, positions); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (typeof val === "string") { + this._implicitLonghandSetter("margin-bottom", val); + } } }, get() { diff --git a/lib/properties/marginLeft.js b/lib/properties/marginLeft.js index 5b1cfdeb..b6cf948c 100644 --- a/lib/properties/marginLeft.js +++ b/lib/properties/marginLeft.js @@ -2,7 +2,7 @@ const parsers = require("../parsers"); -const positions = ["top", "right", "bottom", "left"]; +module.exports.position = "left"; module.exports.parse = function parse(v, opt = {}) { const { globalObject } = opt; @@ -42,7 +42,12 @@ module.exports.definition = { this._setProperty("margin", ""); this._setProperty("margin-left", v); } else { - this._subImplicitSetter("margin", "left", v, module.exports.parse, positions); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (typeof val === "string") { + this._implicitLonghandSetter("margin-left", val); + } } }, get() { diff --git a/lib/properties/marginRight.js b/lib/properties/marginRight.js index 5bc7d9b5..d1fac29e 100644 --- a/lib/properties/marginRight.js +++ b/lib/properties/marginRight.js @@ -2,7 +2,7 @@ const parsers = require("../parsers"); -const positions = ["top", "right", "bottom", "left"]; +module.exports.position = "right"; module.exports.parse = function parse(v, opt = {}) { const { globalObject } = opt; @@ -39,10 +39,15 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._setProperty("margin", ""); this._setProperty("margin-right", v); + this._setProperty("margin", ""); } else { - this._subImplicitSetter("margin", "right", v, module.exports.parse, positions); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (typeof val === "string") { + this._implicitLonghandSetter("margin-right", val); + } } }, get() { diff --git a/lib/properties/marginTop.js b/lib/properties/marginTop.js index c63ab88b..a4c1c595 100644 --- a/lib/properties/marginTop.js +++ b/lib/properties/marginTop.js @@ -2,7 +2,7 @@ const parsers = require("../parsers"); -const positions = ["top", "right", "bottom", "left"]; +module.exports.position = "top"; module.exports.parse = function parse(v, opt = {}) { const { globalObject } = opt; @@ -39,10 +39,15 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._setProperty("margin", ""); this._setProperty("margin-top", v); + this._setProperty("margin", ""); } else { - this._subImplicitSetter("margin", "top", v, module.exports.parse, positions); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (typeof val === "string") { + this._implicitLonghandSetter("margin-top", val); + } } }, get() { diff --git a/lib/properties/padding.js b/lib/properties/padding.js index 2ebbde64..e8d33ee7 100644 --- a/lib/properties/padding.js +++ b/lib/properties/padding.js @@ -1,8 +1,19 @@ "use strict"; const parsers = require("../parsers"); +const paddingTop = require("./paddingTop"); +const paddingRight = require("./paddingRight"); +const paddingBottom = require("./paddingBottom"); +const paddingLeft = require("./paddingLeft"); -const positions = ["top", "right", "bottom", "left"]; +module.exports.position = "all"; + +module.exports.shorthandFor = new Map([ + ["padding-top", paddingTop], + ["padding-right", paddingRight], + ["padding-bottom", paddingBottom], + ["padding-left", paddingLeft] +]); module.exports.parse = function parse(v, opt = {}) { const { globalObject } = opt; @@ -50,7 +61,7 @@ module.exports.parse = function parse(v, opt = {}) { parsedValues.push(values); } if (parsedValues.length) { - return parsedValues.join(" "); + return parsedValues; } }; @@ -58,21 +69,21 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._implicitSetter("padding", "", "", module.exports.parse, positions); + for (const [longhand] of module.exports.shorthandFor) { + this._setProperty(longhand, ""); + } this._setProperty("padding", v); } else { - this._implicitSetter("padding", "", v, module.exports.parse, positions); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (Array.isArray(val) || typeof val === "string") { + this._implicitShorthandSetter("padding", val); + } } }, get() { - const val = this._implicitGetter("padding", positions); - if (val === "") { - return this.getPropertyValue("padding"); - } - if (parsers.hasVarFunc(val)) { - return ""; - } - return val; + return this.getPropertyValue("padding"); }, enumerable: true, configurable: true diff --git a/lib/properties/paddingBottom.js b/lib/properties/paddingBottom.js index eba939b2..86b2e6b9 100644 --- a/lib/properties/paddingBottom.js +++ b/lib/properties/paddingBottom.js @@ -2,7 +2,7 @@ const parsers = require("../parsers"); -const positions = ["top", "right", "bottom", "left"]; +module.exports.position = "bottom"; module.exports.parse = function parse(v, opt = {}) { const { globalObject } = opt; @@ -40,10 +40,15 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._setProperty("padding", ""); this._setProperty("padding-bottom", v); + this._setProperty("padding", ""); } else { - this._subImplicitSetter("padding", "bottom", v, module.exports.parse, positions); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (typeof val === "string") { + this._implicitLonghandSetter("padding-bottom", val); + } } }, get() { diff --git a/lib/properties/paddingLeft.js b/lib/properties/paddingLeft.js index b7dd4ad9..78d8513e 100644 --- a/lib/properties/paddingLeft.js +++ b/lib/properties/paddingLeft.js @@ -2,7 +2,7 @@ const parsers = require("../parsers"); -const positions = ["top", "right", "bottom", "left"]; +module.exports.position = "left"; module.exports.parse = function parse(v, opt = {}) { const { globalObject } = opt; @@ -40,10 +40,15 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._setProperty("padding", ""); this._setProperty("padding-left", v); + this._setProperty("padding", ""); } else { - this._subImplicitSetter("padding", "left", v, module.exports.parse, positions); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (typeof val === "string") { + this._implicitLonghandSetter("padding-left", val); + } } }, get() { diff --git a/lib/properties/paddingRight.js b/lib/properties/paddingRight.js index 3d4bfb00..c61e139e 100644 --- a/lib/properties/paddingRight.js +++ b/lib/properties/paddingRight.js @@ -2,7 +2,7 @@ const parsers = require("../parsers"); -const positions = ["top", "right", "bottom", "left"]; +module.exports.position = "right"; module.exports.parse = function parse(v, opt = {}) { const { globalObject } = opt; @@ -40,10 +40,15 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._setProperty("padding", ""); this._setProperty("padding-right", v); + this._setProperty("padding", ""); } else { - this._subImplicitSetter("padding", "right", v, module.exports.parse, positions); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (typeof val === "string") { + this._implicitLonghandSetter("padding-right", val); + } } }, get() { diff --git a/lib/properties/paddingTop.js b/lib/properties/paddingTop.js index 0729e3e7..e3308502 100644 --- a/lib/properties/paddingTop.js +++ b/lib/properties/paddingTop.js @@ -2,7 +2,7 @@ const parsers = require("../parsers"); -const positions = ["top", "right", "bottom", "left"]; +module.exports.position = "top"; module.exports.parse = function parse(v, opt = {}) { const { globalObject } = opt; @@ -40,10 +40,15 @@ module.exports.definition = { set(v) { v = parsers.prepareValue(v, this._global); if (parsers.hasVarFunc(v)) { - this._setProperty("padding", ""); this._setProperty("padding-top", v); + this._setProperty("padding", ""); } else { - this._subImplicitSetter("padding", "top", v, module.exports.parse, positions); + const val = module.exports.parse(v, { + globalObject: this._global + }); + if (typeof val === "string") { + this._implicitLonghandSetter("padding-top", val); + } } }, get() { diff --git a/test/CSSStyleDeclaration.test.js b/test/CSSStyleDeclaration.test.js index b4d0ff11..cdf0a344 100644 --- a/test/CSSStyleDeclaration.test.js +++ b/test/CSSStyleDeclaration.test.js @@ -760,6 +760,26 @@ describe("CSSStyleDeclaration", () => { assert.strictEqual(style.height, "auto"); }); + it("Shorthand serialization with just longhands", () => { + const style = new CSSStyleDeclaration(); + style.cssText = "margin-right: 10px; margin-left: 10px; margin-top: 10px; margin-bottom: 10px;"; + assert.strictEqual(style.cssText, "margin: 10px;"); + assert.strictEqual(style.margin, "10px"); + + style.cssText = + "margin-right: 10px; margin-left: 10px; margin-top: 10px; margin-bottom: 10px!important;"; + assert.strictEqual( + style.cssText, + "margin-right: 10px; margin-left: 10px; margin-top: 10px; margin-bottom: 10px !important;" + ); + assert.strictEqual(style.margin, ""); + + style.cssText = + "margin-right: 10px !important; margin-left: 10px !important; margin-top: 10px !important; margin-bottom: 10px!important;"; + assert.strictEqual(style.cssText, "margin: 10px !important;"); + assert.strictEqual(style.margin, "10px"); + }); + it("padding and margin should set/clear shorthand properties", () => { const style = new CSSStyleDeclaration(); const parts = ["Top", "Right", "Bottom", "Left"]; @@ -827,7 +847,6 @@ describe("CSSStyleDeclaration", () => { it("removing and setting individual margin properties updates the combined property accordingly", () => { const style = new CSSStyleDeclaration(); style.margin = "1px 2px 3px 4px"; - style.marginTop = ""; assert.strictEqual(style.margin, ""); assert.strictEqual(style.marginRight, "2px"); @@ -886,7 +905,6 @@ describe("CSSStyleDeclaration", () => { style[`${property}Right`] = "4px"; style[`${property}Bottom`] = "5px"; style[`${property}Left`] = "6px"; - assert.strictEqual(style.cssText.includes(importantProperty), true); assert.strictEqual(style.cssText.includes(`${property}-right: 4px;`), true); assert.strictEqual(style.cssText.includes(`${property}-bottom: 5px;`), true); @@ -897,9 +915,7 @@ describe("CSSStyleDeclaration", () => { it(`setting individual ${property} keeps important status of others`, () => { const style = new CSSStyleDeclaration(); style.cssText = `${property}: 3px !important;`; - style[`${property}Top`] = "4px"; - assert.strictEqual(style.cssText.includes(`${property}-top: 4px;`), true); assert.strictEqual(style.cssText.includes(`${property}-right: 3px !important;`), true); assert.strictEqual(style.cssText.includes(`${property}-bottom: 3px !important;`), true);