diff --git a/.gitignore b/.gitignore index 8297d482..3348a32e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules npm-debug.log lib/generated/implementedProperties.js lib/generated/properties.js +lib/generated/propertyDefinitions.js diff --git a/lib/CSSStyleDeclaration.js b/lib/CSSStyleDeclaration.js index 6d430623..e4d18388 100644 --- a/lib/CSSStyleDeclaration.js +++ b/lib/CSSStyleDeclaration.js @@ -10,7 +10,7 @@ const generatedProperties = require("./generated/properties"); const { borderProperties, getPositionValue, - normalizeBorderProperties, + normalizeProperties, prepareBorderProperties, prepareProperties, shorthandProperties @@ -150,7 +150,7 @@ class CSSStyleDeclaration { } 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) { diff --git a/lib/normalize.js b/lib/normalize.js index 6f7b1624..9ec9b661 100644 --- a/lib/normalize.js +++ b/lib/normalize.js @@ -1,6 +1,6 @@ "use strict"; -const implementedProperties = require("./generated/implementedProperties"); +const propertyDefinitions = require("./generated/propertyDefinitions"); const { hasVarFunc, isGlobalKeyword, isValidPropertyValue, splitValue } = require("./parsers"); const background = require("./properties/background"); const border = require("./properties/border"); @@ -16,38 +16,66 @@ const font = require("./properties/font"); const margin = require("./properties/margin"); const padding = require("./properties/padding"); -const borderImageProperty = "border-image"; +/* constants */ +const BACKGROUND = "background"; +const BACKGROUND_COLOR = "background-color"; +const BACKGROUND_SIZE = "background-size"; +const BORDER = "border"; +const BORDER_BOTTOM = "border-bottom"; +const BORDER_COLOR = "border-color"; +const BORDER_IMAGE = "border-image"; +const BORDER_LEFT = "border-left"; +const BORDER_RIGHT = "border-right"; +const BORDER_STYLE = "border-style"; +const BORDER_TOP = "border-top"; +const BORDER_WIDTH = "border-width"; +const TOP = "top"; +const RIGHT = "right"; +const BOTTOM = "bottom"; +const LEFT = "left"; +const WIDTH = "width"; +const STYLE = "style"; +const COLOR = "color"; +const NONE = "none"; +const TRBL_INDICES = { + [TOP]: 0, + [RIGHT]: 1, + [BOTTOM]: 2, + [LEFT]: 3 +}; -exports.shorthandProperties = new Map([ - ["background", background], +/* shorthands */ +const shorthandProperties = new Map([ + [BACKGROUND, background], [ - "border", + BORDER, { definition: border.definition, parse: border.parse, shorthandFor: new Map([ ...border.shorthandFor, ...border.positionShorthandFor, - [borderImageProperty, null] + [BORDER_IMAGE, null] ]) } ], - ["border-width", borderWidth], - ["border-style", borderStyle], - ["border-color", borderColor], - ["border-top", borderTop], - ["border-right", borderRight], - ["border-bottom", borderBottom], - ["border-left", borderLeft], + [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] ]); -exports.borderProperties = new Set([ - "border", - borderImageProperty, +/* borders */ +const borderProperties = new Set([ + BORDER, + BORDER_IMAGE, ...border.shorthandFor.keys(), ...border.positionShorthandFor.keys(), ...borderTop.shorthandFor.keys(), @@ -55,114 +83,164 @@ exports.borderProperties = new Set([ ...borderBottom.shorthandFor.keys(), ...borderLeft.shorthandFor.keys() ]); +const borderPositions = new Set([TOP, RIGHT, BOTTOM, LEFT]); +const borderLines = new Set([WIDTH, STYLE, COLOR]); -exports.getPositionValue = (positionValues, position) => { - switch (positionValues.length) { - case 1: { - const [val1] = positionValues; - return val1; +/** + * Ensures consistent object shape. + * + * @param {string} property - The property name. + * @param {string} [value=""] - The property value. + * @param {string} [priority=""] - The priority. + * @returns {Object} The property item object. + */ +const createPropertyItem = (property, value = "", priority = "") => ({ + property, + value, + priority +}); + +/** + * Retrieves a property item from the map or creates a default one if it doesn't exist. + * + * @param {string} property - The name of the property. + * @param {Map} properties - The map containing all properties. + * @returns {Object} The property item containing name, value, and priority. + */ +const getPropertyItem = (property, properties) => { + const propertyItem = properties.get(property) ?? createPropertyItem(property); + return propertyItem; +}; + +/** + * Calculates the value for a specific position (top, right, bottom, left) + * based on the array of values provided for a shorthand property. + * + * @param {string[]} positionValues - The values extracted from the shorthand property. + * @param {string} position - The specific position (top, right, bottom, left) to retrieve. + * @returns {string} The calculated value for the position. + */ +const getPositionValue = (positionValues, position) => { + const [val1, val2, val3, val4] = positionValues; + const index = TRBL_INDICES[position] ?? -1; + // If a specific position (top, right, bottom, left) is requested. + if (index !== -1) { + switch (positionValues.length) { + case 2: { + // Index 0 (Top) & 2 (Bottom) -> val1 + // Index 1 (Right) & 3 (Left) -> val2 + return index % 2 === 0 ? val1 : val2; + } + case 3: { + // Index 0 (Top) -> val1 + // Index 1 (Right) & 3 (Left) -> val2 + // Index 2 (Bottom) -> val3 + if (index === 2) { + return val3; + } + return index % 2 === 0 ? val1 : val2; + } + case 4: { + return positionValues[index]; + } + case 1: + default: { + return val1; + } } + } + // Fallback logic for when no specific position is requested. + switch (positionValues.length) { 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}`; - } + if (val1 === val2) { + return val1; } + return `${val1} ${val2}`; } case 3: { - const [val1, val2, val3] = positionValues; - switch (position) { - case "top": { + if (val1 === val3) { + if (val1 === val2) { 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}`; - } + 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}`; + if (val2 === val4) { + if (val1 === val3) { + if (val1 === val2) { + return val1; } - return `${val1} ${val2} ${val3} ${val4}`; + return `${val1} ${val2}`; } + return `${val1} ${val2} ${val3}`; } + return `${val1} ${val2} ${val3} ${val4}`; + } + case 1: + default: { + return val1; } - default: } }; -const borderElements = { - name: "border", - positions: ["top", "right", "bottom", "left"], - lines: ["width", "style", "color"] -}; - -const getPropertyItem = (property, properties) => { - const propertyItem = properties.get(property) ?? { - property, - value: "", - priority: "" - }; - return propertyItem; +/** + * Replaces the background shorthand property based on individual longhand values. + * + * @param {string} property - The specific background longhand property being updated. + * @param {Map} properties - The map of all properties. + * @param {Object} opt - Parsing options including global object and configurations. + * @returns {string} The constructed background shorthand string. + */ +const replaceBackgroundShorthand = (property, properties, opt) => { + const { value: propertyValue } = properties.get(property); + const parsedValue = background.shorthandFor.get(property).parse(propertyValue, opt); + const values = splitValue(parsedValue, { + delimiter: "," + }); + const { value: shorthandValue } = properties.get(BACKGROUND); + const bgValues = background.parse(shorthandValue, opt); + const bgLength = bgValues.length; + if (property === BACKGROUND_COLOR) { + bgValues[bgLength - 1][property] = parsedValue[0]; + } else { + for (let i = 0; i < bgLength; i++) { + bgValues[i][property] = values[i]; + } + } + const backgrounds = []; + for (const bgValue of bgValues) { + const bg = []; + for (const [longhand, value] of Object.entries(bgValue)) { + if (!value || value === background.initialValues.get(longhand)) { + continue; + } + if (longhand === BACKGROUND_SIZE) { + bg.push(`/ ${value}`); + } else { + bg.push(value); + } + } + backgrounds.push(bg.join(" ")); + } + return backgrounds.join(", "); }; +/** + * Checks if a property value matches the value within a border shorthand. + * + * @param {string} property - The property to check. + * @param {string} value - The value to compare. + * @param {string} shorthandValue - The shorthand string to parse and compare against. + * @param {Object} [opt={}] - Parsing options. + * @returns {boolean} True if the value matches the shorthand's value. + */ const matchesBorderShorthandValue = (property, value, shorthandValue, opt = {}) => { - const { globalObject } = opt; + const { globalObject, options } = opt; const obj = border.parse(shorthandValue, { - globalObject + globalObject, + options }); if (Object.hasOwn(obj, property)) { return value === obj[property]; @@ -170,17 +248,25 @@ const matchesBorderShorthandValue = (property, value, shorthandValue, opt = {}) return value === border.initialValues.get(property); }; +/** + * Replaces or updates a value within a border shorthand string. + * + * @param {string} value - The new value to insert. + * @param {string} shorthandValue - The existing shorthand string. + * @param {Object} [opt={}] - Parsing options. + * @returns {string} The updated border shorthand string. + */ const replaceBorderShorthandValue = (value, shorthandValue, opt = {}) => { - const { globalObject } = opt; + const { globalObject, options } = opt; const borderFirstInitialKey = border.initialValues.keys().next().value; const borderFirstInitialValue = border.initialValues.get(borderFirstInitialKey); - const valueObj = border.parse(value, { - globalObject - }); + const parseOpt = { + globalObject, + options + }; + const valueObj = border.parse(value, parseOpt); const shorthandObj = shorthandValue - ? border.parse(shorthandValue, { - globalObject - }) + ? border.parse(shorthandValue, parseOpt) : { [borderFirstInitialKey]: borderFirstInitialValue }; @@ -212,932 +298,957 @@ const replaceBorderShorthandValue = (value, shorthandValue, opt = {}) => { return Object.values(shorthandObj).join(" "); }; +/** + * Replaces a value at a specific position (top, right, bottom, left) within a position shorthand. + * + * @param {string} value - The new value to set. + * @param {string[]} positionValues - The array of existing position values. + * @param {string} position - The position to update. + * @returns {string} The updated shorthand string. + */ const replacePositionValue = (value, positionValues, position) => { - switch (positionValues.length) { - case 1: { - const [val1] = positionValues; - if (val1 === value) { - return positionValues.join(" "); - } - 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] = positionValues; - if (val1 === val2) { - return replacePositionValue(value, [val1], position); - } - switch (position) { - case "top": { - if (val1 === value) { - return positionValues.join(" "); + const index = TRBL_INDICES[position] ?? -1; + let currentValues = positionValues; + if (index !== -1) { + // Loop for reducing array length (instead of recursion) + while (true) { + const [val1, val2, val3, val4] = currentValues; + switch (currentValues.length) { + case 2: { + if (val1 === val2) { + currentValues = [val1]; + continue; + } + switch (index) { + // Top + case 0: { + if (val1 === value) { + return currentValues.join(" "); + } + return `${value} ${val2} ${val1}`; + } + // Right + case 1: { + if (val2 === value) { + return currentValues.join(" "); + } + return `${val1} ${value} ${val1} ${val2}`; + } + // Bottom + case 2: { + if (val1 === value) { + return currentValues.join(" "); + } + return `${val1} ${val2} ${value}`; + } + // Left + case 3: + default: { + if (val2 === value) { + return currentValues.join(" "); + } + return `${val1} ${val2} ${val1} ${value}`; + } } - return [value, val2, val1].join(" "); } - case "right": { - if (val2 === value) { - return positionValues.join(" "); + case 3: { + if (val1 === val3) { + currentValues = [val1, val2]; + continue; + } + switch (index) { + // Top + case 0: { + if (val1 === value) { + return currentValues.join(" "); + } else if (val3 === value) { + return `${value} ${val2}`; + } + return `${value} ${val2} ${val3}`; + } + // Right + case 1: { + if (val2 === value) { + return currentValues.join(" "); + } + return `${val1} ${value} ${val3} ${val2}`; + } + // Bottom + case 2: { + if (val3 === value) { + return currentValues.join(" "); + } else if (val1 === value) { + return `${val1} ${val2}`; + } + return `${val1} ${val2} ${value}`; + } + // Left + case 3: + default: { + if (val2 === value) { + return currentValues.join(" "); + } + return `${val1} ${val2} ${val3} ${value}`; + } } - return [val1, value, val1, val2].join(" "); } - case "bottom": { - if (val1 === value) { - return positionValues.join(" "); + case 4: { + if (val2 === val4) { + currentValues = [val1, val2, val3]; + continue; + } + switch (index) { + // Top + case 0: { + if (val1 === value) { + return currentValues.join(" "); + } + return `${value} ${val2} ${val3} ${val4}`; + } + // Right + case 1: { + if (val2 === value) { + return currentValues.join(" "); + } else if (val4 === value) { + return `${val1} ${value} ${val3}`; + } + return `${val1} ${value} ${val3} ${val4}`; + } + // Bottom + case 2: { + if (val3 === value) { + return currentValues.join(" "); + } + return `${val1} ${val2} ${value} ${val4}`; + } + // Left + case 3: + default: { + if (val4 === value) { + return currentValues.join(" "); + } else if (val2 === value) { + return `${val1} ${val2} ${val3}`; + } + return `${val1} ${val2} ${val3} ${value}`; + } } - return [val1, val2, value].join(" "); } - case "left": { - if (val2 === value) { - return positionValues.join(" "); + case 1: + default: { + const [val] = currentValues; + if (val === value) { + return currentValues.join(" "); + } + switch (index) { + // Top + case 0: { + return `${value} ${val} ${val}`; + } + // Right + case 1: { + return `${val} ${value} ${val} ${val}`; + } + // Bottom + case 2: { + return `${val} ${val} ${value}`; + } + // Left + case 3: + default: { + return `${val} ${val} ${val} ${value}`; + } } - return [val1, val2, val1, value].join(" "); } - default: } - break; + } + } + // Fallback logic for when no specific position is requested. + const [val1, val2, val3, val4] = currentValues; + switch (currentValues.length) { + case 2: { + if (val1 === val2) { + return val1; + } + return `${val1} ${val2}`; } case 3: { - const [val1, val2, val3] = positionValues; if (val1 === val3) { - return replacePositionValue(value, [val1, val2], position); - } - switch (position) { - case "top": { - if (val1 === value) { - return positionValues.join(" "); - } else if (val3 === value) { - return [value, val2].join(" "); - } - return [value, val2, val3].join(" "); - } - case "right": { - if (val2 === value) { - return positionValues.join(" "); - } - return [val1, value, val3, val2].join(" "); - } - case "bottom": { - if (val3 === value) { - return positionValues.join(" "); - } else if (val1 === value) { - return [val1, val2].join(" "); - } - return [val1, val2, value].join(" "); - } - case "left": { - if (val2 === value) { - return positionValues.join(" "); - } - return [val1, val2, val3, value].join(" "); + if (val1 === val2) { + return val1; } - default: + return `${val1} ${val2}`; } - break; + return `${val1} ${val2} ${val3}`; } case 4: { - const [val1, val2, val3, val4] = positionValues; if (val2 === val4) { - return replacePositionValue(value, [val1, val2, val3], position); - } - switch (position) { - case "top": { - if (val1 === value) { - return positionValues.join(" "); - } - return [value, val2, val3, val4].join(" "); - } - case "right": { - if (val2 === value) { - return positionValues.join(" "); - } else if (val4 === value) { - return [val1, value, val3].join(" "); - } - return [val1, value, val3, val4].join(" "); - } - case "bottom": { - if (val3 === value) { - return positionValues.join(" "); - } - return [val1, val2, value, val4].join(" "); - } - case "left": { - if (val4 === value) { - return positionValues.join(" "); - } else if (val2 === value) { - return [val1, val2, val3].join(" "); + if (val1 === val3) { + if (val1 === val2) { + return val1; } - return [val1, val2, val3, value].join(" "); + return `${val1} ${val2}`; } - default: + return `${val1} ${val2} ${val3}`; } - break; + return `${val1} ${val2} ${val3} ${val4}`; + } + case 1: + default: { + return val1; } - default: } }; -exports.prepareBorderProperties = (property, value, priority, properties, opt = {}) => { - if (typeof property !== "string" || value === null) { - return; - } - const { globalObject } = opt; - const { lines, name, positions } = borderElements; - const [prop1, prop2, prop3] = property.split("-"); - if (prop1 !== name) { - return; - } else if (positions.includes(prop2)) { - if (prop3) { - if (!lines.includes(prop3)) { - return; +/** + * Handles border property preparation when the value is a string. + * + * @param {Object} params - The parameters object. + * @param {string} params.property - The property name. + * @param {string} params.value - The property value. + * @param {string} params.priority - The property priority. + * @param {Map} params.properties - The map of properties. + * @param {Object} params.parts - The split property name parts. + * @param {Object} params.opt - Parsing options. + * @param {Map} params.borderItems - The map to store processed border items. + */ +const prepareBorderStringValue = ({ + property, + value, + priority, + properties, + parts, + opt, + borderItems +}) => { + const { prop1, prop2, prop3 } = parts; + const { globalObject, options } = opt; + const parseOpt = { + globalObject, + options + }; + const shorthandItem = getPropertyItem(BORDER, properties); + const imageItem = getPropertyItem(BORDER_IMAGE, properties); + // Handle longhand properties. + if (prop3) { + 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; + longhandItem.priority = priority; + const propertyValue = hasVarFunc(value) ? "" : value; + if (propertyValue === "") { + shorthandItem.value = ""; + lineItem.value = ""; + positionItem.value = ""; + } else if (isGlobalKeyword(propertyValue)) { + if (shorthandItem.value !== propertyValue) { + shorthandItem.value = ""; } - } - } else if (lines.includes(prop2)) { - if (prop3) { - return; - } - } - const borderItems = new Map(); - const nameProperty = prop1; - // Empty string, global keywords, var(), value of longhands. - if (typeof value === "string") { - // longhand properties - if (prop3) { - const nameItem = getPropertyItem(nameProperty, properties); - const imageItem = getPropertyItem(borderImageProperty, 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; - longhandItem.priority = priority; - const propertyValue = hasVarFunc(value) ? "" : value; - if (propertyValue === "") { - nameItem.value = ""; + if (lineItem.value !== propertyValue) { lineItem.value = ""; + } + if (positionItem.value !== propertyValue) { 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 && - !matchesBorderShorthandValue(lineProperty, propertyValue, nameItem.value, { - globalObject - }) - ) { - nameItem.value = ""; - } - if (lineItem.value) { - lineItem.value = replacePositionValue(propertyValue, splitValue(lineItem.value), prop2); - } - if ( - positionItem.value && - !matchesBorderShorthandValue(lineProperty, propertyValue, positionItem.value, { - globalObject - }) - ) { - positionItem.value = ""; - } } - borderItems.set(nameProperty, nameItem); - borderItems.set(borderImageProperty, 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(borderImageProperty, 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; - positionItem.priority = priority; - const propertyValue = hasVarFunc(value) ? "" : value; - if (propertyValue === "") { - nameItem.value = ""; + } else { + if ( + shorthandItem.value && + !matchesBorderShorthandValue(lineProperty, propertyValue, shorthandItem.value, parseOpt) + ) { + shorthandItem.value = ""; + } + if (lineItem.value) { + lineItem.value = replacePositionValue(propertyValue, splitValue(lineItem.value), prop2); + } + if ( + positionItem.value && + !matchesBorderShorthandValue(lineProperty, propertyValue, positionItem.value, parseOpt) + ) { + positionItem.value = ""; + } + } + borderItems.set(BORDER, shorthandItem); + borderItems.set(BORDER_IMAGE, imageItem); + borderItems.set(lineProperty, lineItem); + borderItems.set(positionProperty, positionItem); + borderItems.set(longhandProperty, longhandItem); + // Handle side-specific border shorthands (border-top, border-right, border-bottom, border-left). + } else if (prop2 && borderPositions.has(prop2)) { + 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; + positionItem.priority = priority; + const propertyValue = hasVarFunc(value) ? "" : value; + if (propertyValue === "") { + shorthandItem.value = ""; + lineWidthItem.value = ""; + lineStyleItem.value = ""; + lineColorItem.value = ""; + } else if (isGlobalKeyword(propertyValue)) { + if (shorthandItem.value !== propertyValue) { + shorthandItem.value = ""; + } + if (lineWidthItem.value !== propertyValue) { lineWidthItem.value = ""; + } + if (lineStyleItem.value !== propertyValue) { lineStyleItem.value = ""; + } + if (lineColorItem.value !== propertyValue) { 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 && - !matchesBorderShorthandValue(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; - longhandItem.priority = priority; - borderItems.set(longhandProperty, longhandItem); + } else { + if ( + shorthandItem.value && + !matchesBorderShorthandValue(property, propertyValue, shorthandItem.value, parseOpt) + ) { + shorthandItem.value = ""; + } + if ( + lineWidthItem.value && + isValidPropertyValue(lineWidthProperty, propertyValue, globalObject) + ) { + lineWidthItem.value = propertyValue; + } + if ( + lineStyleItem.value && + isValidPropertyValue(lineStyleProperty, propertyValue, globalObject) + ) { + lineStyleItem.value = propertyValue; + } + if ( + lineColorItem.value && + isValidPropertyValue(lineColorProperty, propertyValue, globalObject) + ) { + lineColorItem.value = propertyValue; } - borderItems.set(nameProperty, nameItem); - borderItems.set(borderImageProperty, imageItem); - borderItems.set(lineWidthProperty, lineWidthItem); - borderItems.set(lineStyleProperty, lineStyleItem); - borderItems.set(lineColorProperty, lineColorItem); + } + for (const line of borderLines) { + const longhandProperty = `${prop1}-${prop2}-${line}`; + const longhandItem = getPropertyItem(longhandProperty, properties); + longhandItem.value = propertyValue; + longhandItem.priority = priority; + borderItems.set(longhandProperty, longhandItem); + } + borderItems.set(BORDER, shorthandItem); + borderItems.set(BORDER_IMAGE, imageItem); + borderItems.set(lineWidthProperty, lineWidthItem); + borderItems.set(lineStyleProperty, lineStyleItem); + borderItems.set(lineColorProperty, lineColorItem); + borderItems.set(positionProperty, positionItem); + // Handle property-specific border shorthands (border-width, border-style, border-color). + } else if (prop2 && borderLines.has(prop2)) { + const lineProperty = `${prop1}-${prop2}`; + const lineItem = getPropertyItem(lineProperty, properties); + lineItem.value = value; + lineItem.priority = priority; + const propertyValue = hasVarFunc(value) ? "" : value; + if (propertyValue === "") { + shorthandItem.value = ""; + } else if (isGlobalKeyword(propertyValue)) { + if (shorthandItem.value !== propertyValue) { + shorthandItem.value = ""; + } + } + for (const position of borderPositions) { + const positionProperty = `${prop1}-${position}`; + const positionItem = getPropertyItem(positionProperty, properties); + const longhandProperty = `${prop1}-${position}-${prop2}`; + const longhandItem = getPropertyItem(longhandProperty, properties); + if (propertyValue) { + positionItem.value = replaceBorderShorthandValue( + propertyValue, + positionItem.value, + parseOpt + ); + } else { + positionItem.value = ""; + } + longhandItem.value = propertyValue; + longhandItem.priority = priority; borderItems.set(positionProperty, positionItem); - // border-width, border-style, border-color - } else if (prop2 && lines.includes(prop2)) { - const nameItem = getPropertyItem(nameProperty, properties); - const imageItem = getPropertyItem(borderImageProperty, properties); - const lineProperty = `${prop1}-${prop2}`; + borderItems.set(longhandProperty, longhandItem); + } + borderItems.set(BORDER, shorthandItem); + borderItems.set(BORDER_IMAGE, imageItem); + borderItems.set(lineProperty, lineItem); + // Handle border shorthand. + } else { + const propertyValue = hasVarFunc(value) ? "" : value; + imageItem.value = propertyValue ? NONE : ""; + for (const line of borderLines) { + const lineProperty = `${prop1}-${line}`; const lineItem = getPropertyItem(lineProperty, properties); - lineItem.value = value; + lineItem.value = propertyValue; lineItem.priority = priority; - 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}`; + borderItems.set(lineProperty, lineItem); + } + for (const position of borderPositions) { + const positionProperty = `${prop1}-${position}`; + const positionItem = getPropertyItem(positionProperty, properties); + positionItem.value = propertyValue; + positionItem.priority = priority; + borderItems.set(positionProperty, positionItem); + for (const line of borderLines) { + const longhandProperty = `${positionProperty}-${line}`; const longhandItem = getPropertyItem(longhandProperty, properties); - if (propertyValue) { - positionItem.value = replaceBorderShorthandValue(propertyValue, positionItem.value, { - globalObject - }); - } else { - positionItem.value = ""; - } longhandItem.value = propertyValue; longhandItem.priority = priority; - borderItems.set(positionProperty, positionItem); borderItems.set(longhandProperty, longhandItem); } - borderItems.set(nameProperty, nameItem); - borderItems.set(borderImageProperty, imageItem); - borderItems.set(lineProperty, lineItem); - // border shorthand - } else { - const nameItem = getPropertyItem(nameProperty, properties); - const imageItem = getPropertyItem(borderImageProperty, 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; - lineItem.priority = priority; - borderItems.set(lineProperty, lineItem); - } - for (const position of positions) { - const positionProperty = `${prop1}-${position}`; - const positionItem = getPropertyItem(positionProperty, properties); - positionItem.value = propertyValue; - positionItem.priority = priority; - borderItems.set(positionProperty, positionItem); - for (const line of lines) { - const longhandProperty = `${positionProperty}-${line}`; - const longhandItem = getPropertyItem(longhandProperty, properties); - longhandItem.value = propertyValue; - longhandItem.priority = priority; - borderItems.set(longhandProperty, longhandItem); - } + } + borderItems.set(property, shorthandItem); + borderItems.set(BORDER_IMAGE, imageItem); + } +}; + +/** + * Handles border property preparation when the value is an array. + * + * @param {Object} params - The parameters object. + * @param {Array} params.value - The property value. + * @param {string} params.priority - The property priority. + * @param {Map} params.properties - The map of properties. + * @param {Object} params.parts - The split property name parts. + * @param {Object} params.opt - Parsing options. + * @param {Map} params.borderItems - The map to store processed border items. + */ +const prepareBorderArrayValue = ({ value, priority, properties, parts, opt, borderItems }) => { + const { prop1, prop2 } = parts; + const { globalObject, options } = opt; + const parseOpt = { + globalObject, + options + }; + if (!value.length || !borderLines.has(prop2)) { + return; + } + const shorthandItem = getPropertyItem(BORDER, properties); + const imageItem = getPropertyItem(BORDER_IMAGE, properties); + const lineProperty = `${prop1}-${prop2}`; + const lineItem = getPropertyItem(lineProperty, properties); + if (value.length === 1) { + const [propertyValue] = value; + if (shorthandItem.value) { + if (hasVarFunc(shorthandItem.value)) { + shorthandItem.value = ""; + } else if (propertyValue) { + shorthandItem.value = replaceBorderShorthandValue( + propertyValue, + shorthandItem.value, + parseOpt + ); } - borderItems.set(property, nameItem); - borderItems.set(borderImageProperty, imageItem); } - // Values of border-width, border-style, border-color - } else if (Array.isArray(value)) { - if (!value.length || !lines.includes(prop2)) { - return; + } else { + shorthandItem.value = ""; + } + lineItem.value = value.join(" "); + lineItem.priority = priority; + const positionValues = {}; + const [val1, val2, val3, val4] = value; + switch (value.length) { + case 2: { + positionValues.top = val1; + positionValues.right = val2; + positionValues.bottom = val1; + positionValues.left = val2; + break; + } + case 3: { + positionValues.top = val1; + positionValues.right = val2; + positionValues.bottom = val3; + positionValues.left = val2; + break; + } + case 4: { + positionValues.top = val1; + positionValues.right = val2; + positionValues.bottom = val3; + positionValues.left = val4; + break; + } + case 1: + default: { + positionValues.top = val1; + positionValues.right = val1; + positionValues.bottom = val1; + positionValues.left = val1; + } + } + for (const position of borderPositions) { + const positionProperty = `${prop1}-${position}`; + const positionItem = getPropertyItem(positionProperty, properties); + if (positionItem.value && positionValues[position]) { + positionItem.value = replaceBorderShorthandValue( + positionValues[position], + positionItem.value, + parseOpt + ); } - const nameItem = getPropertyItem(nameProperty, properties); - const imageItem = getPropertyItem(borderImageProperty, properties); - const lineProperty = `${prop1}-${prop2}`; - const lineItem = getPropertyItem(lineProperty, properties); - if (value.length === 1) { - const [propertyValue] = value; - if (nameItem.value && propertyValue) { - nameItem.value = replaceBorderShorthandValue(propertyValue, nameItem.value, { - globalObject - }); - } - } else { - nameItem.value = ""; + const longhandProperty = `${positionProperty}-${prop2}`; + const longhandItem = getPropertyItem(longhandProperty, properties); + longhandItem.value = positionValues[position]; + longhandItem.priority = priority; + borderItems.set(positionProperty, positionItem); + borderItems.set(longhandProperty, longhandItem); + } + borderItems.set(BORDER, shorthandItem); + borderItems.set(BORDER_IMAGE, imageItem); + borderItems.set(lineProperty, lineItem); +}; + +/** + * Handles border property preparation when the value is an object. + * + * @param {Object} params - The parameters object. + * @param {string} params.property - The property name. + * @param {Object} params.value - The property value. + * @param {string} params.priority - The property priority. + * @param {Map} params.properties - The map of properties. + * @param {Object} params.parts - The split property name parts. + * @param {Object} params.opt - Parsing options. + * @param {Map} params.borderItems - The map to store processed border items. + */ +const prepareBorderObjectValue = ({ + property, + value, + priority, + properties, + parts, + opt, + borderItems +}) => { + const { prop1, prop2 } = parts; + const { globalObject, options } = opt; + const parseOpt = { + globalObject, + options + }; + const imageItem = getPropertyItem(BORDER_IMAGE, properties); + // Handle position shorthands. + if (prop2) { + if (!borderPositions.has(prop2)) { + return; } - lineItem.value = value.join(" "); - lineItem.priority = priority; - 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; + const shorthandItem = getPropertyItem(BORDER, 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 (shorthandItem.value) { + for (const positionValue of Object.values(value)) { + if (!matchesBorderShorthandValue(property, positionValue, shorthandItem.value, parseOpt)) { + shorthandItem.value = ""; + break; + } } } - for (const position of positions) { - const positionProperty = `${prop1}-${position}`; - const positionItem = getPropertyItem(positionProperty, properties); - if (positionItem.value && positionValues[position]) { - positionItem.value = replaceBorderShorthandValue( - positionValues[position], - positionItem.value, - { - globalObject - } + positionItem.value = Object.values(value).join(" "); + positionItem.priority = priority; + for (const line of borderLines) { + const longhandProperty = `${prop1}-${prop2}-${line}`; + const longhandItem = getPropertyItem(longhandProperty, properties); + const itemValue = Object.hasOwn(value, longhandProperty) + ? value[longhandProperty] + : border.initialValues.get(`${prop1}-${line}`); + if (line === WIDTH && lineWidthItem.value) { + lineWidthItem.value = replacePositionValue( + itemValue, + splitValue(lineWidthItem.value), + prop2 + ); + } else if (line === STYLE && lineStyleItem.value) { + lineStyleItem.value = replacePositionValue( + itemValue, + splitValue(lineStyleItem.value), + prop2 + ); + } else if (line === COLOR && lineColorItem.value) { + lineColorItem.value = replacePositionValue( + itemValue, + splitValue(lineColorItem.value), + prop2 ); } - const longhandProperty = `${positionProperty}-${prop2}`; - const longhandItem = getPropertyItem(longhandProperty, properties); - longhandItem.value = positionValues[position]; + longhandItem.value = itemValue; longhandItem.priority = priority; - borderItems.set(positionProperty, positionItem); borderItems.set(longhandProperty, longhandItem); } - borderItems.set(nameProperty, nameItem); - borderItems.set(borderImageProperty, 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(borderImageProperty, 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}`; + borderItems.set(BORDER, shorthandItem); + borderItems.set(BORDER_IMAGE, imageItem); + borderItems.set(lineWidthProperty, lineWidthItem); + borderItems.set(lineStyleProperty, lineStyleItem); + borderItems.set(lineColorProperty, lineColorItem); + borderItems.set(positionProperty, positionItem); + // Handle border shorthand. + } else { + const shorthandItem = getPropertyItem(prop1, 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(" "); + shorthandItem.value = propertyValue; + shorthandItem.priority = priority; + imageItem.value = propertyValue ? NONE : ""; + if (Object.hasOwn(value, lineWidthProperty)) { + lineWidthItem.value = value[lineWidthProperty]; + } else { + lineWidthItem.value = border.initialValues.get(lineWidthProperty); + } + lineWidthItem.priority = priority; + if (Object.hasOwn(value, lineStyleProperty)) { + lineStyleItem.value = value[lineStyleProperty]; + } else { + lineStyleItem.value = border.initialValues.get(lineStyleProperty); + } + lineStyleItem.priority = priority; + if (Object.hasOwn(value, lineColorProperty)) { + lineColorItem.value = value[lineColorProperty]; + } else { + lineColorItem.value = border.initialValues.get(lineColorProperty); + } + lineColorItem.priority = priority; + for (const position of borderPositions) { + const positionProperty = `${prop1}-${position}`; const positionItem = getPropertyItem(positionProperty, properties); - if (nameItem.value) { - for (const positionValue of Object.values(value)) { - if ( - !matchesBorderShorthandValue(property, positionValue, nameItem.value, { - globalObject - }) - ) { - nameItem.value = ""; - break; - } - } - } - positionItem.value = Object.values(value).join(" "); + positionItem.value = propertyValue; positionItem.priority = priority; - for (const line of lines) { - const longhandProperty = `${prop1}-${prop2}-${line}`; + for (const line of borderLines) { + const longhandProperty = `${positionProperty}-${line}`; const longhandItem = getPropertyItem(longhandProperty, properties); - if (Object.hasOwn(value, longhandProperty)) { - const itemValue = value[longhandProperty]; - if (line === "width") { - if (lineWidthItem.value) { - lineWidthItem.value = replacePositionValue( - itemValue, - splitValue(lineWidthItem.value), - prop2 - ); - } - } else if (line === "style") { - if (lineStyleItem.value) { - lineStyleItem.value = replacePositionValue( - itemValue, - splitValue(lineStyleItem.value), - prop2 - ); - } - } else if (line === "color") { - if (lineColorItem.value) { - lineColorItem.value = replacePositionValue( - itemValue, - splitValue(lineColorItem.value), - prop2 - ); - } - } - longhandItem.value = itemValue; - longhandItem.priority = priority; + const lineProperty = `${prop1}-${line}`; + if (Object.hasOwn(value, lineProperty)) { + longhandItem.value = value[lineProperty]; } else { - const itemValue = border.initialValues.get(`${prop1}-${line}`); - if (line === "width") { - if (lineWidthItem.value) { - lineWidthItem.value = replacePositionValue( - itemValue, - splitValue(lineWidthItem.value), - prop2 - ); - } - } else if (line === "style") { - if (lineStyleItem.value) { - lineStyleItem.value = replacePositionValue( - itemValue, - splitValue(lineStyleItem.value), - prop2 - ); - } - } else if (line === "color") { - if (lineColorItem.value) { - lineColorItem.value = replacePositionValue( - itemValue, - splitValue(lineColorItem.value), - prop2 - ); - } - } - longhandItem.value = itemValue; - longhandItem.priority = priority; + longhandItem.value = border.initialValues.get(lineProperty); } + longhandItem.priority = priority; borderItems.set(longhandProperty, longhandItem); } - borderItems.set(nameProperty, nameItem); - borderItems.set(borderImageProperty, imageItem); - borderItems.set(lineWidthProperty, lineWidthItem); - borderItems.set(lineStyleProperty, lineStyleItem); - borderItems.set(lineColorProperty, lineColorItem); borderItems.set(positionProperty, positionItem); - // border shorthand + } + borderItems.set(property, shorthandItem); + borderItems.set(BORDER_IMAGE, imageItem); + borderItems.set(lineWidthProperty, lineWidthItem); + borderItems.set(lineStyleProperty, lineStyleItem); + borderItems.set(lineColorProperty, lineColorItem); + } +}; + +/** + * Prepares border properties by splitting shorthands and handling updates. + * + * @param {string} property - The border property name. + * @param {string|Array|Object} value - The value of the property. + * @param {string} priority - The priority of the property (e.g., "important"). + * @param {Map} properties - The map of all properties. + * @param {Object} [opt={}] - Parsing options. + * @returns {Map|undefined} A map of expanded or updated border properties. + */ +const prepareBorderProperties = (property, value, priority, properties, opt = {}) => { + if (typeof property !== "string" || value === null) { + return; + } + if (!property.startsWith(BORDER)) { + return; + } + let prop2, prop3; + if (property.length > BORDER.length) { + // Check if next char is '-' + if (property.charCodeAt(BORDER.length) !== 45) { + return; + } + // property is like "border-..." + const remainder = property.substring(BORDER.length + 1); + const hyphenIndex = remainder.indexOf("-"); + if (hyphenIndex !== -1) { + prop2 = remainder.substring(0, hyphenIndex); + prop3 = remainder.substring(hyphenIndex + 1); } else { - const nameItem = getPropertyItem(prop1, properties); - const imageItem = getPropertyItem(borderImageProperty, 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; - nameItem.priority = priority; - imageItem.value = propertyValue ? "none" : ""; - if (Object.hasOwn(value, lineWidthProperty)) { - lineWidthItem.value = value[lineWidthProperty]; - } else { - lineWidthItem.value = border.initialValues.get(lineWidthProperty); - } - lineWidthItem.priority = priority; - if (Object.hasOwn(value, lineStyleProperty)) { - lineStyleItem.value = value[lineStyleProperty]; - } else { - lineStyleItem.value = border.initialValues.get(lineStyleProperty); - } - lineStyleItem.priority = priority; - if (Object.hasOwn(value, lineColorProperty)) { - lineColorItem.value = value[lineColorProperty]; - } else { - lineColorItem.value = border.initialValues.get(lineColorProperty); - } - lineColorItem.priority = priority; - for (const position of positions) { - const positionProperty = `${prop1}-${position}`; - const positionItem = getPropertyItem(positionProperty, properties); - positionItem.value = propertyValue; - positionItem.priority = priority; - 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); - } - longhandItem.priority = priority; - borderItems.set(longhandProperty, longhandItem); - } - borderItems.set(positionProperty, positionItem); - } - borderItems.set(property, nameItem); - borderItems.set(borderImageProperty, imageItem); - borderItems.set(lineWidthProperty, lineWidthItem); - borderItems.set(lineStyleProperty, lineStyleItem); - borderItems.set(lineColorProperty, lineColorItem); + prop2 = remainder; } - } else { + } + if ( + (borderPositions.has(prop2) && prop3 && !borderLines.has(prop3)) || + (borderLines.has(prop2) && prop3) + ) { return; } - if (!borderItems.has(name)) { + const parts = { + prop1: BORDER, + prop2, + prop3 + }; + const borderItems = new Map(); + if (typeof value === "string") { + prepareBorderStringValue({ + property, + value, + priority, + properties, + parts, + opt, + borderItems + }); + } else if (Array.isArray(value)) { + prepareBorderArrayValue({ + value, + priority, + properties, + parts, + opt, + borderItems + }); + } else if (value && typeof value === "object") { + prepareBorderObjectValue({ + property, + value, + priority, + properties, + parts, + opt, + borderItems + }); + } + if (!borderItems.has(BORDER)) { 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: "" - }; - borderProperties.set(lineProperty, lineItem); + const borderProps = new Map([[BORDER, borderItems.get(BORDER)]]); + for (const line of borderLines) { + const lineProperty = `${BORDER}-${line}`; + const lineItem = borderItems.get(lineProperty) ?? getPropertyItem(lineProperty, properties); + borderProps.set(lineProperty, lineItem); } - for (const position of positions) { - const positionProperty = `${name}-${position}`; - const positionItem = borderItems.get(positionProperty) ?? - properties.get(positionProperty) ?? { - property: positionProperty, - value: "", - priority: "" - }; - 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: "" - }; - borderProperties.set(longhandProperty, longhandItem); + for (const position of borderPositions) { + const positionProperty = `${BORDER}-${position}`; + const positionItem = + borderItems.get(positionProperty) ?? getPropertyItem(positionProperty, properties); + borderProps.set(positionProperty, positionItem); + for (const line of borderLines) { + const longhandProperty = `${BORDER}-${position}-${line}`; + const longhandItem = + borderItems.get(longhandProperty) ?? getPropertyItem(longhandProperty, properties); + borderProps.set(longhandProperty, longhandItem); } } - const borderImageItem = borderItems.get(borderImageProperty) ?? { - property: borderImageProperty, - value: "", - priority: "" - }; - borderProperties.set(borderImageProperty, borderImageItem); - return borderProperties; + const borderImageItem = borderItems.get(BORDER_IMAGE) ?? createPropertyItem(BORDER_IMAGE); + borderProps.set(BORDER_IMAGE, borderImageItem); + return borderProps; }; -const generateBorderLineShorthand = (items, property, prior) => { +/** + * Generates a border line shorthand property if all line components are present. + * + * @param {Map} items - The map of collected property items. + * @param {string} property - The shorthand property name to generate. + * @param {string} [priority=""] - The priority of the property. + * @returns {Array} A key-value pair for the generated property. + */ +const generateBorderLineShorthand = (items, property, priority = "") => { const values = []; for (const [, item] of items) { const { value: itemValue } = item; values.push(itemValue); } - const value = exports.getPositionValue(values); - const priority = prior ? prior : ""; - return [property, { property, value, priority }]; + const value = getPositionValue(values); + return [property, createPropertyItem(property, value, priority)]; }; -const generateBorderPositionShorthand = (items, property, prior) => { +/** + * Generates a border position shorthand property if all position components are present. + * + * @param {Map} items - The map of collected property items. + * @param {string} property - The shorthand property name to generate. + * @param {string} [priority=""] - The priority of the property. + * @returns {Array} A key-value pair for the generated property. + */ +const generateBorderPositionShorthand = (items, property, priority = "") => { const values = []; for (const [, item] of items) { const { value: itemValue } = item; values.push(itemValue); } const value = values.join(" "); - const priority = prior ? prior : ""; - return [property, { property, value, priority }]; + return [property, createPropertyItem(property, value, priority)]; }; -const generateBorderNameShorthand = (items, property, prior) => { +/** + * Generates a border shorthand property if all components match. + * + * @param {Array} items - The collection of property values. + * @param {string} property - The shorthand property name to generate. + * @param {string} [priority=""] - The priority of the property. + * @returns {Array|undefined} A key-value pair for the generated property or undefined. + */ +const generateBorderShorthand = (items, property, priority = "") => { const values = new Set(items); if (values.size === 1) { const value = values.keys().next().value; - const priority = prior ? prior : ""; - return [property, { property, value, priority }]; + return [property, createPropertyItem(property, value, priority)]; } }; -const prepareBorderShorthands = (properties) => { - const lineWidthItems = new Map(); - const lineWidthPriorItems = new Map(); - const lineStyleItems = new Map(); - const lineStylePriorItems = new Map(); - const lineColorItems = new Map(); - const lineColorPriorItems = new Map(); - const positionTopItems = new Map(); - const positionTopPriorItems = new Map(); - const positionRightItems = new Map(); - const positionRightPriorItems = new Map(); - const positionBottomItems = new Map(); - const positionBottomPriorItems = new Map(); - const positionLeftItems = new Map(); - const positionLeftPriorItems = new Map(); - for (const [property, { priority, value }] of properties) { - const [, positionPart, linePart] = property.split("-"); - switch (linePart) { - case "width": { - if (priority) { - lineWidthPriorItems.set(property, { property, value, priority }); - } else { - lineWidthItems.set(property, { property, value, priority }); - } - break; - } - case "style": { - if (priority) { - lineStylePriorItems.set(property, { property, value, priority }); - } else { - lineStyleItems.set(property, { property, value, priority }); - } - break; - } - case "color": { - if (priority) { - lineColorPriorItems.set(property, { property, value, priority }); - } else { - lineColorItems.set(property, { property, value, priority }); - } - break; - } - default: - } - switch (positionPart) { - case "top": { - if (priority) { - positionTopPriorItems.set(property, { property, value, priority }); - } else { - positionTopItems.set(property, { property, value, priority }); - } - break; - } - case "right": { - if (priority) { - positionRightPriorItems.set(property, { property, value, priority }); - } else { - positionRightItems.set(property, { property, value, priority }); - } - break; - } - case "bottom": { - if (priority) { - positionBottomPriorItems.set(property, { property, value, priority }); - } else { - positionBottomItems.set(property, { property, value, priority }); - } - break; - } - case "left": { - if (priority) { - positionLeftPriorItems.set(property, { property, value, priority }); - } else { - positionLeftItems.set(property, { property, value, priority }); - } - break; - } - default: - } - } - if (lineWidthItems.size === 4) { - const [property, item] = generateBorderLineShorthand(lineWidthItems, "border-width") ?? []; - if (property && item) { - properties.set(property, item); - } - } else if (lineWidthPriorItems.size === 4) { - const [property, item] = - generateBorderLineShorthand(lineWidthPriorItems, "border-width", "important") ?? []; - if (property && item) { - properties.set(property, item); - } - } - if (lineStyleItems.size === 4) { - const [property, item] = generateBorderLineShorthand(lineStyleItems, "border-style") ?? []; - if (property && item) { - properties.set(property, item); - } - } else if (lineStylePriorItems.size === 4) { - const [property, item] = - generateBorderLineShorthand(lineStylePriorItems, "border-style", "important") ?? []; - if (property && item) { - properties.set(property, item); - } +const borderCollectionConfig = { + [WIDTH]: { + shorthand: BORDER_WIDTH, + generator: generateBorderLineShorthand + }, + [STYLE]: { + shorthand: BORDER_STYLE, + generator: generateBorderLineShorthand + }, + [COLOR]: { + shorthand: BORDER_COLOR, + generator: generateBorderLineShorthand + }, + [TOP]: { + shorthand: BORDER_TOP, + generator: generateBorderPositionShorthand + }, + [RIGHT]: { + shorthand: BORDER_RIGHT, + generator: generateBorderPositionShorthand + }, + [BOTTOM]: { + shorthand: BORDER_BOTTOM, + generator: generateBorderPositionShorthand + }, + [LEFT]: { + shorthand: BORDER_LEFT, + generator: generateBorderPositionShorthand } - if (lineColorItems.size === 4) { - const [property, item] = generateBorderLineShorthand(lineColorItems, "border-color") ?? []; - if (property && item) { - properties.set(property, item); - } - } else if (lineColorPriorItems.size === 4) { - const [property, item] = - generateBorderLineShorthand(lineColorPriorItems, "border-color", "important") ?? []; - if (property && item) { - properties.set(property, item); - } - } - const nameItems = []; - const namePriorItems = []; - if (positionTopItems.size === 3) { - const [property, item] = generateBorderPositionShorthand(positionTopItems, "border-top") ?? []; - if (property && item) { - properties.set(property, item); - if (properties.has(borderImageProperty)) { - const { value: imageValue } = properties.get(borderImageProperty); - if (imageValue === "none") { - const { value: itemValue } = item; - nameItems.push(itemValue); - } - } - } - } else if (positionTopPriorItems.size === 3) { - const [property, item] = - generateBorderPositionShorthand(positionTopPriorItems, "border-top", "important") ?? []; - if (property && item) { - properties.set(property, item); - if (properties.has(borderImageProperty)) { - const { value: imageValue } = properties.get(borderImageProperty); - if (imageValue === "none") { - const { value: itemValue } = item; - namePriorItems.push(itemValue); - } - } - } +}; + +/** + * Processes and consolidates border-related longhands into shorthands where possible. + * + * @param {Map} properties - The map of current properties. + * @returns {Map} The updated map with consolidated border properties. + */ +const prepareBorderShorthands = (properties) => { + const borderCollections = {}; + for (const key of Object.keys(borderCollectionConfig)) { + borderCollections[key] = { + ...borderCollectionConfig[key], + items: new Map(), + priorityItems: new Map() + }; } - if (positionRightItems.size === 3) { - const [property, item] = - generateBorderPositionShorthand(positionRightItems, "border-right") ?? []; - if (property && item) { - properties.set(property, item); - if (properties.has(borderImageProperty)) { - const { value: imageValue } = properties.get(borderImageProperty); - if (imageValue === "none") { - const { value: itemValue } = item; - nameItems.push(itemValue); - } - } - } - } else if (positionRightPriorItems.size === 3) { - const [property, item] = - generateBorderPositionShorthand(positionRightPriorItems, "border-right", "important") ?? []; - if (property && item) { - properties.set(property, item); - if (properties.has(borderImageProperty)) { - const { value: imageValue } = properties.get(borderImageProperty); - if (imageValue === "none") { - const { value: itemValue } = item; - nameItems.push(itemValue); - } + for (const [property, item] of properties) { + const { priority, value } = item; + let positionPart, linePart; + // We can assume property starts with "border-" + const firstHyphen = property.indexOf("-"); + if (firstHyphen !== -1) { + const remainder = property.substring(firstHyphen + 1); + const secondHyphen = remainder.indexOf("-"); + if (secondHyphen !== -1) { + positionPart = remainder.substring(0, secondHyphen); + linePart = remainder.substring(secondHyphen + 1); + } else { + positionPart = remainder; + linePart = undefined; } } - } - if (positionBottomItems.size === 3) { - const [property, item] = - generateBorderPositionShorthand(positionBottomItems, "border-bottom") ?? []; - if (property && item) { - properties.set(property, item); - if (properties.has(borderImageProperty)) { - const { value: imageValue } = properties.get(borderImageProperty); - if (imageValue === "none") { - const { value: itemValue } = item; - nameItems.push(itemValue); - } + if (linePart && borderCollections[linePart]) { + const collection = borderCollections[linePart]; + if (priority) { + collection.priorityItems.set(property, { property, value, priority }); + } else { + collection.items.set(property, { property, value, priority }); } } - } else if (positionBottomPriorItems.size === 3) { - const [property, item] = - generateBorderPositionShorthand(positionBottomPriorItems, "border-bottom", "important") ?? []; - if (property && item) { - properties.set(property, item); - if (properties.has(borderImageProperty)) { - const { value: imageValue } = properties.get(borderImageProperty); - if (imageValue === "none") { - const { value: itemValue } = item; - nameItems.push(itemValue); - } + if (positionPart && borderCollections[positionPart]) { + const collection = borderCollections[positionPart]; + if (priority) { + collection.priorityItems.set(property, { property, value, priority }); + } else { + collection.items.set(property, { property, value, priority }); } } } - if (positionLeftItems.size === 3) { - const [property, item] = - generateBorderPositionShorthand(positionLeftItems, "border-left") ?? []; - if (property && item) { - properties.set(property, item); - if (properties.has(borderImageProperty)) { - const { value: imageValue } = properties.get(borderImageProperty); - if (imageValue === "none") { - const { value: itemValue } = item; - nameItems.push(itemValue); + const shorthandItems = []; + const shorthandPriorityItems = []; + for (const [key, collection] of Object.entries(borderCollections)) { + const { shorthand, generator, items, priorityItems } = collection; + const requiredSize = borderLines.has(key) ? 4 : 3; + if (items.size === requiredSize) { + const [property, item] = generator(items, shorthand) ?? []; + if (property && item) { + properties.set(property, item); + if (borderPositions.has(key) && properties.has(BORDER_IMAGE)) { + const { value: imageValue } = properties.get(BORDER_IMAGE); + if (imageValue === NONE) { + shorthandItems.push(item.value); + } } } - } - } else if (positionLeftPriorItems.size === 3) { - const [property, item] = - generateBorderPositionShorthand(positionLeftPriorItems, "border-left", "important") ?? []; - if (property && item) { - properties.set(property, item); - if (properties.has(borderImageProperty)) { - const { value: imageValue } = properties.get(borderImageProperty); - if (imageValue === "none") { - const { value: itemValue } = item; - nameItems.push(itemValue); + } else if (priorityItems.size === requiredSize) { + const [property, item] = generator(priorityItems, shorthand, "important") ?? []; + if (property && item) { + properties.set(property, item); + if (borderPositions.has(key) && properties.has(BORDER_IMAGE)) { + const { value: imageValue } = properties.get(BORDER_IMAGE); + if (imageValue === NONE) { + shorthandPriorityItems.push(item.value); + } } } } } - const mixedPriorities = nameItems.length && namePriorItems.length; - const imageItem = { - property: borderImageProperty, - value: "none", - priority: "" - }; - if (nameItems.length === 4) { - const [property, item] = generateBorderNameShorthand(nameItems, "border") ?? []; + const mixedPriorities = shorthandItems.length && shorthandPriorityItems.length; + const imageItem = createPropertyItem(BORDER_IMAGE, NONE); + if (shorthandItems.length === 4) { + const [property, item] = generateBorderShorthand(shorthandItems, BORDER) ?? []; if (property && item) { properties.set(property, item); - properties.delete(borderImageProperty); - properties.set(borderImageProperty, imageItem); + properties.delete(BORDER_IMAGE); + properties.set(BORDER_IMAGE, imageItem); } - } else if (namePriorItems.length === 4) { + } else if (shorthandPriorityItems.length === 4) { const [property, item] = - generateBorderNameShorthand(namePriorItems, "border", "important") ?? []; + generateBorderShorthand(shorthandPriorityItems, BORDER, "important") ?? []; if (property && item) { properties.set(property, item); - properties.delete(borderImageProperty); - properties.set(borderImageProperty, imageItem); + properties.delete(BORDER_IMAGE); + properties.set(BORDER_IMAGE, imageItem); } - } else if (properties.has(borderImageProperty)) { - const { value: imageValue } = properties.get(borderImageProperty); - if (imageValue === "none") { + } else if (properties.has(BORDER_IMAGE)) { + const { value: imageValue } = properties.get(BORDER_IMAGE); + if (imageValue === NONE) { if (mixedPriorities) { - properties.delete(borderImageProperty); - properties.set(borderImageProperty, imageItem); + properties.delete(BORDER_IMAGE); + properties.set(BORDER_IMAGE, imageItem); } else { - properties.delete(borderImageProperty); + properties.delete(BORDER_IMAGE); } } } if (mixedPriorities) { const items = []; - const priorItems = []; + const priorityItems = []; for (const item of properties) { const [, { priority }] = item; if (priority) { - priorItems.push(item); + priorityItems.push(item); } else { items.push(item); } @@ -1145,52 +1256,210 @@ const prepareBorderShorthands = (properties) => { const firstPropertyKey = properties.keys().next().value; const { priority: firstPropertyPriority } = properties.get(firstPropertyKey); if (firstPropertyPriority) { - return new Map([...priorItems, ...items]); + return new Map([...priorityItems, ...items]); } - return new Map([...items, ...priorItems]); + return new Map([...items, ...priorityItems]); } - if (properties.has(borderImageProperty)) { - properties.delete(borderImageProperty); - properties.set(borderImageProperty, imageItem); + if (properties.has(BORDER_IMAGE)) { + properties.delete(BORDER_IMAGE); + properties.set(BORDER_IMAGE, imageItem); } return properties; }; -exports.prepareProperties = (properties, opt = {}) => { - const { globalObject } = opt; - const { positions } = borderElements; +/** + * Processes shorthand properties from the shorthands map. + * + * @param {Map} shorthands - The map containing shorthand property groups. + * @returns {Map} A map of processed shorthand properties. + */ +const processShorthandProperties = (shorthands) => { + const shorthandItems = new Map(); + for (const [property, item] of shorthands) { + const shorthandItem = shorthandProperties.get(property); + if (item.size === shorthandItem.shorthandFor.size && shorthandItem.position) { + const positionValues = []; + let priority = ""; + for (const { value: longhandValue, priority: longhandPriority } of item.values()) { + positionValues.push(longhandValue); + if (longhandPriority) { + priority = longhandPriority; + } + } + const value = getPositionValue(positionValues, shorthandItem.position); + shorthandItems.set(property, createPropertyItem(property, value, priority)); + } + } + return shorthandItems; +}; + +/** + * Updates longhand properties for all 4 border positions (top, right, bottom, left). + * + * @param {Map} properties - The map of properties to update. + * @param {string} namePart - The property name prefix (e.g., "border"). + * @param {string} linePart - The property line suffix (e.g., "width", "style", "color"). + * @param {string|Array} value - The value to set (string or array for position-based values). + * @param {string} priority - The priority of the property. + */ +const updatePositionLonghands = (properties, namePart, linePart, value, priority) => { + for (const position of borderPositions) { + const longhandProperty = `${namePart}-${position}-${linePart}`; + const longhandValue = Array.isArray(value) ? getPositionValue(value, position) : value; + const longhandItem = createPropertyItem(longhandProperty, longhandValue, priority); + if (properties.has(longhandProperty)) { + const { priority: existingPriority } = properties.get(longhandProperty); + if (!existingPriority) { + properties.delete(longhandProperty); + properties.set(longhandProperty, longhandItem); + } + } else { + properties.set(longhandProperty, longhandItem); + } + } +}; + +/** + * Updates longhand properties based on a values object. + * + * @param {Map} properties - The map of properties to update. + * @param {Object} values - The object containing property keys and values. + * @param {string} priority - The priority of the properties. + * @param {boolean} [safe=false] - Whether to preserve existing priority. + */ +const updateLonghandProperties = (properties, values, priority, safe = false) => { + for (const property of Object.keys(values)) { + const value = values[property]; + const item = createPropertyItem(property, value, priority); + if (safe && properties.has(property)) { + const { priority: existingPriority } = properties.get(property); + if (!existingPriority) { + properties.delete(property); + properties.set(property, item); + } + } else { + properties.set(property, item); + } + } +}; + +/** + * Processes border properties from the borders map, expanding and normalizing them. + * + * @param {Map} borders - The map containing accumulated border properties. + * @param {Object} parseOpt - Options for parsing values. + * @returns {Map} A map of fully processed and normalized border properties. + */ +const processBorderProperties = (borders, parseOpt) => { + const longhandProperties = new Map(); + for (const [property, item] of borders) { + if (shorthandProperties.has(property)) { + const { value, priority } = item; + if (property === BORDER) { + const lineItems = border.parse(value, parseOpt); + for (const [key, initialValue] of border.initialValues) { + if (!Object.hasOwn(lineItems, key)) { + lineItems[key] = initialValue; + } + } + for (const lineProperty of Object.keys(lineItems)) { + let namePart, linePart; + const hyphenIndex = lineProperty.indexOf("-"); + if (hyphenIndex !== -1) { + namePart = lineProperty.substring(0, hyphenIndex); + linePart = lineProperty.substring(hyphenIndex + 1); + } else { + // fallback for safety, though lineProperty from border.parse keys + // should have hyphen + namePart = lineProperty; + linePart = ""; + } + const lineValue = lineItems[lineProperty]; + updatePositionLonghands(longhandProperties, namePart, linePart, lineValue, priority); + } + if (value) { + longhandProperties.set(BORDER_IMAGE, createPropertyItem(BORDER_IMAGE, NONE, priority)); + } + } else { + const shorthandItem = shorthandProperties.get(property); + const parsedItem = shorthandItem.parse(value, parseOpt); + if (Array.isArray(parsedItem)) { + let namePart, linePart; + const hyphenIndex = property.indexOf("-"); + if (hyphenIndex !== -1) { + namePart = property.substring(0, hyphenIndex); + linePart = property.substring(hyphenIndex + 1); + } else { + namePart = property; + } + updatePositionLonghands(longhandProperties, namePart, linePart, parsedItem, priority); + } else if (parsedItem) { + for (const [key, initialValue] of shorthandItem.initialValues) { + if (!Object.hasOwn(parsedItem, key)) { + parsedItem[key] = initialValue; + } + } + updateLonghandProperties(longhandProperties, parsedItem, priority, true); + } + } + } else if (longhandProperties.has(property)) { + const { priority } = longhandProperties.get(property); + if (!priority) { + longhandProperties.delete(property); + longhandProperties.set(property, item); + } + } else { + longhandProperties.set(property, item); + } + } + const borderItems = prepareBorderShorthands(longhandProperties); + return borderItems; +}; + +/** + * Normalize and prepare CSS properties, handling shorthands and longhands. + * + * @param {Map} properties - The initial map of properties. + * @param {Object} [opt={}] - Parsing options. + * @returns {Map} The normalized map of properties. + */ +const prepareProperties = (properties, opt = {}) => { + const { globalObject, options } = opt; + const parseOpt = { + globalObject, + options + }; const parsedProperties = new Map(); - const prepareShorthands = new Map(); - const borderProperties = new Map(); + const shorthands = new Map(); + const borders = new Map(); + let hasPrecedingBackground = false; for (const [property, item] of properties) { const { value, priority } = item; - const { logicalPropertyGroup: shorthandProperty } = implementedProperties.get(property) ?? {}; - if (exports.borderProperties.has(property)) { - borderProperties.set(property, { property, value, priority }); - } else if (exports.shorthandProperties.has(shorthandProperty)) { - if (!prepareShorthands.has(shorthandProperty)) { - prepareShorthands.set(shorthandProperty, new Map()); + const { logicalPropertyGroup: shorthandProperty } = propertyDefinitions.get(property) ?? {}; + if (borderProperties.has(property)) { + borders.set(property, { property, value, priority }); + } else if (shorthandProperties.has(shorthandProperty)) { + if (!shorthands.has(shorthandProperty)) { + shorthands.set(shorthandProperty, new Map()); } - const longhandItems = prepareShorthands.get(shorthandProperty); + const longhandItems = shorthands.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 }); - prepareShorthands.set(shorthandProperty, longhandItems); + shorthands.set(shorthandProperty, longhandItems); } else { parsedProperties.delete(shorthandProperty); } } else { longhandItems.set(property, { property, value, priority }); - prepareShorthands.set(shorthandProperty, longhandItems); + shorthands.set(shorthandProperty, longhandItems); } parsedProperties.set(property, item); - } else if (exports.shorthandProperties.has(property)) { - const shorthandItem = exports.shorthandProperties.get(property); - const parsedValues = shorthandItem.parse(value, { - globalObject - }); + } else if (shorthandProperties.has(property)) { + const shorthandItem = shorthandProperties.get(property); + const parsedValues = shorthandItem.parse(value, parseOpt); let omitShorthandProperty = false; if (Array.isArray(parsedValues)) { const [parsedValue] = parsedValues; @@ -1204,204 +1473,94 @@ exports.prepareProperties = (properties, opt = {}) => { } } const { position } = longhandItem; - const longhandValue = exports.getPositionValue([parsedValue], position); - parsedProperties.set(longhandProperty, { - property: longhandProperty, - value: longhandValue, - priority - }); + const longhandValue = getPositionValue([parsedValue], position); + parsedProperties.set( + longhandProperty, + createPropertyItem(longhandProperty, longhandValue, priority) + ); } } else if (parsedValue) { - for (const longhandProperty of Object.keys(parsedValue)) { - const longhandValue = parsedValue[longhandProperty]; - parsedProperties.set(longhandProperty, { - property: longhandProperty, - value: longhandValue, - priority - }); - } - } - } else if (parsedValues) { - for (const longhandProperty of Object.keys(parsedValues)) { - const longhandValue = parsedValues[longhandProperty]; - parsedProperties.set(longhandProperty, { - property: longhandProperty, - value: longhandValue, - priority - }); + updateLonghandProperties(parsedProperties, parsedValue, priority); } + } else if (parsedValues && typeof parsedValues !== "string") { + updateLonghandProperties(parsedProperties, parsedValues, priority); } if (!omitShorthandProperty) { - parsedProperties.set(property, { property, value, priority }); + if (property === BACKGROUND) { + hasPrecedingBackground = true; + } + parsedProperties.set(property, createPropertyItem(property, value, priority)); } } else { - parsedProperties.set(property, { property, value, priority }); - } - } - if (prepareShorthands.size) { - for (const [property, item] of prepareShorthands) { - const shorthandItem = 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 = exports.getPositionValue(positionValues, shorthandItem.position); - parsedProperties.set(property, { + parsedProperties.set(property, createPropertyItem(property, value, priority)); + if (hasPrecedingBackground) { + const { value: shorthandValue, priority: shorthandPriority } = properties.get(BACKGROUND); + if ((!shorthandPriority || priority) && !hasVarFunc(shorthandValue)) { + const replacedShorthandValue = replaceBackgroundShorthand( property, - value, - priority - }); + parsedProperties, + parseOpt + ); + properties.delete(BACKGROUND); + properties.set( + BACKGROUND, + createPropertyItem(BACKGROUND, replacedShorthandValue, shorthandPriority) + ); } } } } - if (borderProperties.size) { - const longhandProperties = new Map(); - for (const [property, item] of borderProperties) { - if (exports.shorthandProperties.has(property)) { - const { value, priority } = item; - if (property === "border") { - const lineItems = border.parse(value, { - globalObject - }); - for (const [key, initialValue] of border.initialValues) { - if (!Object.hasOwn(lineItems, key)) { - lineItems[key] = initialValue; - } - } - for (const lineProperty of Object.keys(lineItems)) { - const [namePart, linePart] = lineProperty.split("-"); - const lineValue = lineItems[lineProperty]; - for (const position of positions) { - const longhandProperty = `${namePart}-${position}-${linePart}`; - const longhandItem = { - property: longhandProperty, - value: lineValue, - priority - }; - if (longhandProperties.has(longhandProperty)) { - const { priority: longhandPriority } = longhandProperties.get(longhandProperty); - if (!longhandPriority) { - longhandProperties.delete(longhandProperty); - longhandProperties.set(longhandProperty, longhandItem); - } - } else { - longhandProperties.set(longhandProperty, longhandItem); - } - } - } - if (value) { - longhandProperties.set(borderImageProperty, { - property: borderImageProperty, - value: "none", - priority - }); - } - } else { - const shorthandItem = exports.shorthandProperties.get(property); - const parsedItem = shorthandItem.parse(value, { - globalObject - }); - if (Array.isArray(parsedItem)) { - const [namePart, linePart] = property.split("-"); - for (const position of positions) { - const longhandProperty = `${namePart}-${position}-${linePart}`; - const longhandValue = exports.getPositionValue(parsedItem, position); - const longhandItem = { - property: longhandProperty, - value: longhandValue, - priority - }; - if (longhandProperties.has(longhandProperty)) { - const { priority: longhandPriority } = longhandProperties.get(longhandProperty); - if (!longhandPriority) { - longhandProperties.delete(longhandProperty); - longhandProperties.set(longhandProperty, longhandItem); - } - } else { - longhandProperties.set(longhandProperty, longhandItem); - } - } - } else if (parsedItem) { - for (const [key, initialValue] of shorthandItem.initialValues) { - if (!Object.hasOwn(parsedItem, key)) { - parsedItem[key] = initialValue; - } - } - for (const longhandProperty of Object.keys(parsedItem)) { - const longhandValue = parsedItem[longhandProperty]; - const longhandItem = { - property: longhandProperty, - value: longhandValue, - priority - }; - if (longhandProperties.has(longhandProperty)) { - const { priority: longhandPriority } = longhandProperties.get(longhandProperty); - if (!longhandPriority) { - longhandProperties.delete(longhandProperty); - longhandProperties.set(longhandProperty, longhandItem); - } - } else { - longhandProperties.set(longhandProperty, longhandItem); - } - } - } - } - } else if (longhandProperties.has(property)) { - const { priority } = longhandProperties.get(property); - if (!priority) { - longhandProperties.delete(property); - longhandProperties.set(property, item); - } - } else { - longhandProperties.set(property, item); - } + if (shorthands.size) { + const shorthandItems = processShorthandProperties(shorthands); + for (const [property, item] of shorthandItems) { + parsedProperties.set(property, item); } - const normalizedProperties = prepareBorderShorthands(longhandProperties); - for (const [property, item] of normalizedProperties) { + } + if (borders.size) { + const borderItems = processBorderProperties(borders, parseOpt); + for (const [property, item] of borderItems) { parsedProperties.set(property, item); } } return parsedProperties; }; -exports.normalizeBorderProperties = (properties) => { - const { lines, name, positions } = borderElements; - if (properties.has(name)) { - for (const line of lines) { - properties.delete(`${name}-${line}`); +/** + * Cleans up redundancy in border properties by removing longhands that are covered by shorthands. + * + * @param {Map} properties - The map of properties to normalize. + * @returns {Map} The normalized properties map. + */ +const normalizeProperties = (properties) => { + if (properties.has(BORDER)) { + for (const line of borderLines) { + properties.delete(`${BORDER}-${line}`); } - for (const position of positions) { - properties.delete(`${name}-${position}`); - for (const line of lines) { - properties.delete(`${name}-${position}-${line}`); + for (const position of borderPositions) { + properties.delete(`${BORDER}-${position}`); + for (const line of borderLines) { + properties.delete(`${BORDER}-${position}-${line}`); } } - properties.delete(`${name}-image`); + properties.delete(`${BORDER}-image`); } - for (const line of lines) { - const lineProperty = `${name}-${line}`; + for (const line of borderLines) { + const lineProperty = `${BORDER}-${line}`; if (properties.has(lineProperty)) { - for (const position of positions) { - const positionProperty = `${name}-${position}`; - const longhandProperty = `${name}-${position}-${line}`; + for (const position of borderPositions) { + const positionProperty = `${BORDER}-${position}`; + const longhandProperty = `${BORDER}-${position}-${line}`; properties.delete(positionProperty); properties.delete(longhandProperty); } } } - for (const position of positions) { - const positionProperty = `${name}-${position}`; + for (const position of borderPositions) { + const positionProperty = `${BORDER}-${position}`; if (properties.has(positionProperty)) { const longhandProperties = []; - for (const line of lines) { - const longhandProperty = `${name}-${position}-${line}`; + for (const line of borderLines) { + const longhandProperty = `${BORDER}-${position}-${line}`; longhandProperties.push(longhandProperty); } if (longhandProperties.length === 3) { @@ -1415,3 +1574,12 @@ exports.normalizeBorderProperties = (properties) => { } return properties; }; + +module.exports = { + borderProperties, + getPositionValue, + normalizeProperties, + prepareBorderProperties, + prepareProperties, + shorthandProperties +}; diff --git a/package.json b/package.json index e4d4684b..53e44eec 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "prepare": "run-p prepare:*", "prepare:implemented_properties": "node ./scripts/generateImplementedProperties.mjs", "prepare:properties": "node ./scripts/generateProperties.js", + "prepare:propertyDefinitions": "node ./scripts/generatePropertyDefinitions.mjs", "test": "node --test" }, "license": "MIT", diff --git a/scripts/generatePropertyDefinitions.mjs b/scripts/generatePropertyDefinitions.mjs new file mode 100644 index 00000000..457a8459 --- /dev/null +++ b/scripts/generatePropertyDefinitions.mjs @@ -0,0 +1,25 @@ +import fs from "node:fs"; +import path from "node:path"; +import css from "@webref/css"; +import allProperties from "../lib/generated/allProperties.js"; +import allExtraProperties from "../lib/utils/allExtraProperties.js"; + +const { properties } = await css.listAll(); +const unifiedProperties = + typeof allProperties.union === "function" + ? allProperties.union(allExtraProperties) + : new Set([...allProperties, ...allExtraProperties]); +const definitions = properties + .filter((definition) => unifiedProperties.has(definition.name)) + .sort((a, b) => a.name.localeCompare(b.name)) + .map((definition) => [definition.name, definition]); + +const [dateTodayFormatted] = new Date().toISOString().split("T"); +const output = `"use strict"; +// autogenerated - ${dateTodayFormatted} + +module.exports = new Map(${JSON.stringify(definitions, null, 2)}); +`; +const { dirname } = import.meta; +const outputFile = path.resolve(dirname, "../lib/generated/propertyDefinitions.js"); +fs.writeFileSync(outputFile, output);