Primeiro commit do projeto Angular

This commit is contained in:
2026-03-14 20:41:55 +00:00
parent 9bebe1de72
commit 94f4f46395
22413 changed files with 3221690 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) Chad Walker
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+11
View File
@@ -0,0 +1,11 @@
# CSSStyleDeclaration
A Node.js implementation of the CSS Object Model [`CSSStyleDeclaration` class](https://drafts.csswg.org/cssom/#the-cssstyledeclaration-interface).
## Background
This package is an extension of the `CSSStyleDeclaration` class in Nikita Vasilyev's [CSSOM](https://github.com/NV/CSSOM), with added support for modern specifications. The primary use case is for testing browser code in a Node environment.
It was originally created by Chad Walker, it is now maintained by the jsdom community.
Bug reports and pull requests are welcome.
+613
View File
@@ -0,0 +1,613 @@
/**
* This is a fork from the CSS Style Declaration part of
* https://github.com/NV/CSSOM
*/
"use strict";
const propertyDescriptors = require("./generated/propertyDescriptors");
const {
borderProperties,
getPositionValue,
normalizeProperties,
prepareBorderProperties,
prepareProperties,
shorthandProperties
} = require("./normalize");
const { hasVarFunc, isGlobalKeyword, parseCSS, parsePropertyValue, prepareValue } = require("./parsers");
const { asciiLowercase } = require("./utils/strings");
/**
* @see https://drafts.csswg.org/cssom/#the-cssstyledeclaration-interface
*/
class CSSStyleDeclaration {
/**
* Creates a new CSSStyleDeclaration instance.
*
* @param {Function} [onChangeCallback] - Callback triggered when style changes.
* @param {object} [opt] - Options.
* @param {object} [opt.context] - The context object (Window, Element, or CSSRule).
*/
constructor(onChangeCallback, { context } = {}) {
// Internals for jsdom
this._global = globalThis;
this._onChange = onChangeCallback;
// Internals for CSS declaration block
// @see https://drafts.csswg.org/cssom/#css-declaration-blocks
this._computed = false;
this._ownerNode = null;
this._parentRule = null;
this._readonly = false;
this._updating = false;
// Other internals
this._length = 0;
this._propertyIndices = new Map();
this._priorities = new Map();
this._values = new Map();
if (context) {
if (typeof context.getComputedStyle === "function") {
this._global = context;
this._computed = true;
// FIXME: The `_readonly` flag should initially be `false` to be editable,
// but should eventually be set to `true`.
// this._readonly = true;
} else if (context.nodeType === 1 && Object.hasOwn(context, "style")) {
this._global = context.ownerDocument.defaultView;
this._ownerNode = context;
} else if (Object.hasOwn(context, "parentRule")) {
this._parentRule = context;
// Find Window from the owner node of the StyleSheet.
const window = context?.parentStyleSheet?.ownerNode?.ownerDocument?.defaultView;
if (window) {
this._global = window;
}
}
}
}
/**
* Returns the textual representation of the declaration block.
*
* @returns {string} The serialized CSS text.
*/
get cssText() {
if (this._computed) {
return "";
}
const properties = new Map();
for (let i = 0; i < this._length; i++) {
const property = this[i];
const value = this.getPropertyValue(property);
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 });
}
const normalizedProperties = normalizeProperties(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(" ");
}
/**
* Sets the textual representation of the declaration block.
* This clears all existing properties and parses the new CSS text.
*
* @param {string} text - The new CSS text.
*/
set cssText(text) {
if (this._readonly) {
const msg = "cssText can not be modified.";
const name = "NoModificationAllowedError";
throw new this._global.DOMException(msg, name);
}
this._clearIndexedProperties();
this._values.clear();
this._priorities.clear();
if (this._parentRule || (this._ownerNode && this._updating)) {
return;
}
try {
this._updating = true;
const valueObj = parseCSS(text, { context: "declarationList", parseValue: false });
if (valueObj?.children) {
const properties = new Map();
let shouldSkipNext = false;
for (const item of valueObj.children) {
if (item.type === "Atrule") {
continue;
}
if (item.type === "Rule") {
shouldSkipNext = true;
continue;
}
if (shouldSkipNext === true) {
shouldSkipNext = false;
continue;
}
const {
important,
property,
value: { value }
} = item;
if (typeof property === "string" && typeof value === "string") {
const priority = important ? "important" : "";
const isCustomProperty = property.startsWith("--");
if (isCustomProperty || hasVarFunc(value)) {
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);
if (parsedValue) {
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);
for (const [property, item] of parsedProperties) {
const { priority, value } = item;
this._priorities.set(property, priority);
this.setProperty(property, value, priority);
}
}
} catch {
return;
} finally {
this._updating = false;
}
if (this._onChange) {
this._onChange(this.cssText);
}
}
/**
* Returns the number of properties in the declaration block.
*
* @returns {number} The property count.
*/
get length() {
return this._length;
}
/**
* Returns the CSSRule that is the parent of this declaration block.
*
* @returns {object|null} The parent CSSRule or null.
*/
get parentRule() {
return this._parentRule;
}
/**
* Alias for the "float" property.
*
* @returns {string} The value of the "float" property.
*/
get cssFloat() {
return this.getPropertyValue("float");
}
/**
* Sets the "float" property.
*
* @param {string} value - The new value for "float".
*/
set cssFloat(value) {
this._setProperty("float", value);
}
/**
* Returns the priority of the specified property (e.g. "important").
*
* @param {string} property - The property name.
* @returns {string} The priority string, or empty string if not set.
*/
getPropertyPriority(property) {
return this._priorities.get(property) || "";
}
/**
* Returns the value of the specified property.
*
* @param {string} property - The property name.
* @returns {string} The property value, or empty string if not set.
*/
getPropertyValue(property) {
if (this._values.has(property)) {
return this._values.get(property).toString();
}
return "";
}
/**
* Returns the property name at the specified index.
*
* @param {...number} args - The index (only the first argument is used).
* @returns {string} The property name, or empty string if index is invalid.
*/
item(...args) {
if (!args.length) {
const msg = "1 argument required, but only 0 present.";
throw new this._global.TypeError(msg);
}
const [value] = args;
const index = parseInt(value);
if (Number.isNaN(index) || index < 0 || index >= this._length) {
return "";
}
return this[index];
}
/**
* Removes the specified property from the declaration block.
*
* @param {string} property - The property name to remove.
* @returns {string} The value of the removed property.
*/
removeProperty(property) {
if (this._readonly) {
const msg = `Property ${property} can not be modified.`;
const name = "NoModificationAllowedError";
throw new this._global.DOMException(msg, name);
}
if (!this._values.has(property)) {
return "";
}
const prevValue = this._values.get(property);
this._values.delete(property);
this._priorities.delete(property);
const index = this._getIndexOf(property);
if (index >= 0) {
this._removeIndexedProperty(index);
if (this._onChange) {
this._onChange(this.cssText);
}
}
return prevValue;
}
/**
* Sets a property value with an optional priority.
*
* @param {string} property - The property name.
* @param {string} value - The property value.
* @param {string} [priority=""] - The priority (e.g. "important").
*/
setProperty(property, value, priority = "") {
if (this._readonly) {
const msg = `Property ${property} can not be modified.`;
const name = "NoModificationAllowedError";
throw new this._global.DOMException(msg, name);
}
value = prepareValue(value);
if (value === "") {
if (Object.hasOwn(propertyDescriptors, property)) {
// TODO: Refactor handlers to not require `.call()`.
propertyDescriptors[property].set.call(this, value);
}
this.removeProperty(property);
return;
}
// Custom property
if (property.startsWith("--")) {
this._setProperty(property, value, priority);
return;
}
property = asciiLowercase(property);
if (!Object.hasOwn(propertyDescriptors, property)) {
return;
}
if (priority) {
this._priorities.set(property, priority);
} else {
this._priorities.delete(property);
}
propertyDescriptors[property].set.call(this, value);
}
/**
* Clears all indexed properties, properties indices and resets length to 0.
*
* @private
*/
_clearIndexedProperties() {
this._propertyIndices.clear();
for (let i = 0; i < this._length; i++) {
delete this[i];
}
this._length = 0;
}
/**
* Removes an indexed property at the specified index, shifts others, and updates indices.
*
* @private
* @param {number} index - The index of the property to remove.
*/
_removeIndexedProperty(index) {
this._propertyIndices.delete(this[index]);
for (let i = index; i < this._length - 1; i++) {
const property = this[i + 1];
this[i] = property;
this._propertyIndices.set(property, i);
}
delete this[this._length - 1];
this._length--;
}
/**
* Returns the index of the specified property.
*
* @private
* @param {string} property - The property name to search for.
* @returns {number} The index of the property, or -1 if not found.
*/
_getIndexOf(property) {
return this._propertyIndices.get(property) ?? -1;
}
/**
* Sets a property and update indices.
*
* @private
* @param {string} property - The property name.
* @param {string} value - The property value.
* @param {string} priority - The priority.
*/
_setProperty(property, value, priority) {
if (typeof value !== "string") {
return;
}
if (value === "") {
this.removeProperty(property);
return;
}
let originalText = "";
if (this._onChange && !this._updating) {
originalText = this.cssText;
}
if (!this._values.has(property)) {
// New property.
this[this._length] = property;
this._propertyIndices.set(property, this._length);
this._length++;
}
if (priority === "important") {
this._priorities.set(property, priority);
} else {
this._priorities.delete(property);
}
this._values.set(property, value);
if (this._onChange && !this._updating && this.cssText !== originalText) {
this._onChange(this.cssText);
}
}
/**
* Helper to handle border property expansion.
*
* @private
* @param {string} property - The property name (e.g. "border").
* @param {object|Array|string} value - The value to set.
* @param {string} priority - The priority.
*/
_borderSetter(property, value, priority) {
const properties = new Map();
if (typeof priority !== "string") {
priority = this._priorities.get(property) ?? "";
}
if (property === "border") {
properties.set(property, { propery: property, value, priority });
} else {
for (let i = 0; i < this._length; i++) {
const itemProperty = this[i];
if (borderProperties.has(itemProperty)) {
const itemValue = this.getPropertyValue(itemProperty);
const longhandPriority = this._priorities.get(itemProperty) ?? "";
let itemPriority = longhandPriority;
if (itemProperty === property) {
itemPriority = priority;
}
properties.set(itemProperty, {
property: itemProperty,
value: itemValue,
priority: itemPriority
});
}
}
}
const parsedProperties = prepareBorderProperties(property, value, priority, properties);
for (const [itemProperty, item] of parsedProperties) {
const { priority: itemPriority, value: itemValue } = item;
this._setProperty(itemProperty, itemValue, itemPriority);
}
}
/**
* Helper to handle flexbox shorthand expansion.
*
* @private
* @param {string} property - The property name.
* @param {string} value - The property value.
* @param {string} priority - The priority.
* @param {string} shorthandProperty - The shorthand property name.
*/
_flexBoxSetter(property, value, priority, shorthandProperty) {
if (!shorthandProperty || !shorthandProperties.has(shorthandProperty)) {
return;
}
const shorthandPriority = this._priorities.get(shorthandProperty);
this.removeProperty(shorthandProperty);
if (typeof priority !== "string") {
priority = this._priorities.get(property) ?? "";
}
this.removeProperty(property);
if (shorthandPriority && priority) {
this._setProperty(property, value);
} else {
this._setProperty(property, value, priority);
}
if (value && !hasVarFunc(value)) {
const longhandValues = [];
const shorthandItem = shorthandProperties.get(shorthandProperty);
let hasGlobalKeyword = false;
for (const [longhandProperty] of shorthandItem.shorthandFor) {
if (longhandProperty === property) {
if (isGlobalKeyword(value)) {
hasGlobalKeyword = true;
}
longhandValues.push(value);
} else {
const longhandValue = this.getPropertyValue(longhandProperty);
const longhandPriority = this._priorities.get(longhandProperty) ?? "";
if (!longhandValue || longhandPriority !== priority) {
break;
}
if (isGlobalKeyword(longhandValue)) {
hasGlobalKeyword = true;
}
longhandValues.push(longhandValue);
}
}
if (longhandValues.length === shorthandItem.shorthandFor.size) {
if (hasGlobalKeyword) {
const [firstValue, ...restValues] = longhandValues;
if (restValues.every((val) => val === firstValue)) {
this._setProperty(shorthandProperty, firstValue, priority);
}
} else {
const parsedValue = shorthandItem.parse(longhandValues.join(" "));
const shorthandValue = Object.values(parsedValue).join(" ");
this._setProperty(shorthandProperty, shorthandValue, priority);
}
}
}
}
/**
* Helper to handle position shorthand expansion.
*
* @private
* @param {string} property - The property name.
* @param {Array|string} value - The property value.
* @param {string} priority - The priority.
*/
_positionShorthandSetter(property, value, priority) {
if (!shorthandProperties.has(property)) {
return;
}
const shorthandValues = [];
if (Array.isArray(value)) {
shorthandValues.push(...value);
} else if (typeof value === "string") {
shorthandValues.push(value);
} else {
return;
}
if (typeof priority !== "string") {
priority = this._priorities.get(property) ?? "";
}
const { position, shorthandFor } = shorthandProperties.get(property);
let hasPriority = false;
for (const [longhandProperty, longhandItem] of shorthandFor) {
const { position: longhandPosition } = longhandItem;
const longhandValue = getPositionValue(shorthandValues, longhandPosition);
if (priority) {
this._setProperty(longhandProperty, longhandValue, priority);
} else {
const longhandPriority = this._priorities.get(longhandProperty) ?? "";
if (longhandPriority) {
hasPriority = true;
} else {
this._setProperty(longhandProperty, longhandValue, priority);
}
}
}
if (hasPriority) {
this.removeProperty(property);
} else {
const shorthandValue = getPositionValue(shorthandValues, position);
this._setProperty(property, shorthandValue, priority);
}
}
/**
* Helper to handle position longhand updates affecting shorthands.
*
* @private
* @param {string} property - The property name.
* @param {string} value - The property value.
* @param {string} priority - The priority.
* @param {string} shorthandProperty - The shorthand property name.
*/
_positionLonghandSetter(property, value, priority, shorthandProperty) {
if (!shorthandProperty || !shorthandProperties.has(shorthandProperty)) {
return;
}
const shorthandPriority = this._priorities.get(shorthandProperty);
this.removeProperty(shorthandProperty);
if (typeof priority !== "string") {
priority = this._priorities.get(property) ?? "";
}
this.removeProperty(property);
if (shorthandPriority && priority) {
this._setProperty(property, value);
} else {
this._setProperty(property, value, priority);
}
if (value && !hasVarFunc(value)) {
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);
}
}
}
}
// TODO: Remove once the CSSStyleDeclaration is fully spec-compliant.
// @see https://github.com/jsdom/cssstyle/issues/255#issuecomment-3630183207
Object.defineProperties(CSSStyleDeclaration.prototype, propertyDescriptors);
module.exports = {
CSSStyleDeclaration
};
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+11
View File
@@ -0,0 +1,11 @@
"use strict";
const { CSSStyleDeclaration } = require("./CSSStyleDeclaration");
const propertyDefinitions = require("./generated/propertyDefinitions");
const { parsePropertyValue } = require("./parsers");
module.exports = {
CSSStyleDeclaration,
parsePropertyValue,
propertyDefinitions
};
+1448
View File
File diff suppressed because it is too large Load Diff
+851
View File
@@ -0,0 +1,851 @@
"use strict";
const {
resolve: resolveColor,
utils: { cssCalc, resolveGradient, splitValue }
} = require("@asamuzakjp/css-color");
const { next: syntaxes } = require("@csstools/css-syntax-patches-for-csstree");
const csstree = require("css-tree");
const { LRUCache } = require("lru-cache");
const propertyDefinitions = require("./generated/propertyDefinitions");
const { asciiLowercase } = require("./utils/strings");
// Constants
const CALC_FUNC_NAMES = "(?:a?(?:cos|sin|tan)|abs|atan2|calc|clamp|exp|hypot|log|max|min|mod|pow|rem|round|sign|sqrt)";
// CSS global keywords
// @see https://drafts.csswg.org/css-cascade-5/#defaulting-keywords
const GLOBAL_KEYS = new Set(["initial", "inherit", "unset", "revert", "revert-layer"]);
// System colors
// @see https://drafts.csswg.org/css-color/#css-system-colors
// @see https://drafts.csswg.org/css-color/#deprecated-system-colors
const SYS_COLORS = new Set([
"accentcolor",
"accentcolortext",
"activeborder",
"activecaption",
"activetext",
"appworkspace",
"background",
"buttonborder",
"buttonface",
"buttonhighlight",
"buttonshadow",
"buttontext",
"canvas",
"canvastext",
"captiontext",
"field",
"fieldtext",
"graytext",
"highlight",
"highlighttext",
"inactiveborder",
"inactivecaption",
"inactivecaptiontext",
"infobackground",
"infotext",
"linktext",
"mark",
"marktext",
"menu",
"menutext",
"scrollbar",
"selecteditem",
"selecteditemtext",
"threeddarkshadow",
"threedface",
"threedhighlight",
"threedlightshadow",
"threedshadow",
"visitedtext",
"window",
"windowframe",
"windowtext"
]);
// AST node types
const AST_TYPES = Object.freeze({
CALC: "Calc",
DIMENSION: "Dimension",
FUNCTION: "Function",
GLOBAL_KEYWORD: "GlobalKeyword",
HASH: "Hash",
IDENTIFIER: "Identifier",
NUMBER: "Number",
PERCENTAGE: "Percentage",
STRING: "String",
URL: "Url"
});
// Regular expressions
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\(/;
// Patched css-tree
const cssTree = csstree.fork(syntaxes);
// Instance of the LRU Cache. Stores up to 4096 items.
const lruCache = new LRUCache({
max: 4096
});
/**
* Prepares a stringified value.
*
* @param {string|number|null|undefined} value - The value to prepare.
* @returns {string} The prepared value.
*/
function prepareValue(value) {
// `null` is converted to an empty string.
// @see https://webidl.spec.whatwg.org/#LegacyNullToEmptyString
if (value === null) {
return "";
}
return `${value}`.trim();
}
/**
* Checks if the value is a global keyword.
*
* @param {string} val - The value to check.
* @returns {boolean} True if the value is a global keyword, false otherwise.
*/
function isGlobalKeyword(val) {
return GLOBAL_KEYS.has(asciiLowercase(val));
}
/**
* Checks if the value starts with or contains a CSS var() function.
*
* @param {string} val - The value to check.
* @returns {boolean} True if the value contains a var() function, false otherwise.
*/
function hasVarFunc(val) {
return varRegEx.test(val) || varContainedRegEx.test(val);
}
/**
* Checks if the value starts with or contains CSS calc() or math functions.
*
* @param {string} val - The value to check.
* @returns {boolean} True if the value contains calc() or math functions, false otherwise.
*/
function hasCalcFunc(val) {
return calcRegEx.test(val) || calcContainedRegEx.test(val);
}
/**
* Parses a CSS string into an AST.
*
* @param {string} val - The CSS string to parse.
* @param {object} opt - The options for parsing.
* @returns {object} The AST.
*/
function parseCSS(val, opt) {
return cssTree.parse(prepareValue(val), opt);
}
/**
* Checks if the value is a valid property value.
* Returns false for custom properties or values containing var().
*
* @param {string} prop - The property name.
* @param {string} val - The property value.
* @returns {boolean} True if the value is valid, false otherwise.
*/
function isValidPropertyValue(prop, val) {
if (!propertyDefinitions.has(prop)) {
return false;
}
val = prepareValue(val);
if (val === "") {
return true;
}
const cacheKey = `isValidPropertyValue_${prop}_${val}`;
const cachedValue = lruCache.get(cacheKey);
if (typeof cachedValue === "boolean") {
return cachedValue;
}
let result;
try {
const ast = parseCSS(val, {
context: "value"
});
const { error, matched } = cssTree.lexer.matchProperty(prop, ast);
result = error === null && matched !== null;
} catch {
result = false;
}
lruCache.set(cacheKey, result);
return result;
}
/**
* Resolves CSS math functions.
*
* @param {string} val - The value to resolve.
* @param {object} [opt={ format: "specifiedValue" }] - The options for resolving.
* @returns {string|undefined} The resolved value.
*/
function resolveCalc(val, opt = { format: "specifiedValue" }) {
val = prepareValue(val);
if (val === "" || hasVarFunc(val) || !hasCalcFunc(val)) {
return val;
}
const cacheKey = `resolveCalc_${val}`;
const cachedValue = lruCache.get(cacheKey);
if (typeof cachedValue === "string") {
return cachedValue;
}
const ast = parseCSS(val, { context: "value" });
if (!ast?.children) {
return;
}
const values = [];
for (const item of ast.children) {
const { type: itemType, name: itemName, value: itemValue } = item;
if (itemType === AST_TYPES.FUNCTION) {
const value = cssTree
.generate(item)
.replace(/\)(?!\)|\s|,)/g, ") ")
.trim();
if (calcNameRegEx.test(itemName)) {
const newValue = cssCalc(value, opt);
values.push(newValue);
} else {
values.push(value);
}
} else if (itemType === AST_TYPES.STRING) {
values.push(`"${itemValue}"`);
} else {
values.push(itemName ?? itemValue);
}
}
const resolvedValue = values.join(" ");
lruCache.set(cacheKey, resolvedValue);
return resolvedValue;
}
/**
* Parses a property value.
* Returns a string or an array of parsed objects.
*
* @param {string} prop - The property name.
* @param {string} val - The property value.
* @param {object} [opt={}] - The options for parsing.
* @returns {string|Array<object>|undefined} The parsed value.
*/
function parsePropertyValue(prop, val, opt = {}) {
if (!propertyDefinitions.has(prop)) {
return;
}
const { caseSensitive } = opt;
val = prepareValue(val);
if (val === "" || hasVarFunc(val)) {
return val;
} else if (hasCalcFunc(val)) {
const calculatedValue = resolveCalc(val, {
format: "specifiedValue"
});
if (typeof calculatedValue !== "string") {
return;
}
val = calculatedValue;
}
const cacheKey = `parsePropertyValue_${prop}_${val}_${caseSensitive}`;
const cachedValue = lruCache.get(cacheKey);
if (cachedValue === false) {
return;
} else if (cachedValue !== undefined) {
return cachedValue;
}
let parsedValue;
const lowerCasedValue = asciiLowercase(val);
if (GLOBAL_KEYS.has(lowerCasedValue)) {
parsedValue = [
{
type: AST_TYPES.GLOBAL_KEYWORD,
name: lowerCasedValue
}
];
} else {
try {
const ast = parseCSS(val, {
context: "value"
});
const { error, matched } = cssTree.lexer.matchProperty(prop, ast);
if (error || !matched) {
parsedValue = false;
} else {
const items = ast.children;
const itemCount = items.size;
const values = [];
for (const item of items) {
const { children, name, type, value, unit } = item;
switch (type) {
case AST_TYPES.DIMENSION: {
values.push({
type,
value,
unit: asciiLowercase(unit)
});
break;
}
case AST_TYPES.FUNCTION: {
const raw = itemCount === 1 ? val : cssTree.generate(item).replace(/\)(?!\)|\s|,)/g, ") ");
// Remove "${name}(" from the start and ")" from the end
const itemValue = raw.trim().slice(name.length + 1, -1);
if (name === "calc") {
if (children.size === 1) {
const child = children.first;
if (child.type === AST_TYPES.NUMBER) {
values.push({
type: AST_TYPES.CALC,
name,
isNumber: true,
value: `${parseFloat(child.value)}`
});
} else {
values.push({
type: AST_TYPES.CALC,
name,
isNumber: false,
value: asciiLowercase(itemValue)
});
}
} else {
values.push({
type: AST_TYPES.CALC,
name,
isNumber: false,
value: asciiLowercase(itemValue)
});
}
} else {
values.push({
type,
name,
value: caseSensitive ? itemValue : asciiLowercase(itemValue)
});
}
break;
}
case AST_TYPES.IDENTIFIER: {
if (caseSensitive) {
values.push(item);
} else {
values.push({
type,
name: asciiLowercase(name)
});
}
break;
}
default: {
values.push(item);
}
}
}
parsedValue = values;
}
} catch {
parsedValue = false;
}
}
lruCache.set(cacheKey, parsedValue);
if (parsedValue === false) {
return;
}
return parsedValue;
}
/**
* Parses a numeric value (number, dimension, percentage).
* Helper function for serializeNumber, serializeLength, etc.
*
* @param {Array<object>} val - The AST value.
* @param {object} [opt={}] - The options for parsing.
* @param {Function} validateType - Function to validate the node type.
* @returns {object|undefined} The parsed result containing num and unit, or undefined.
*/
function parseNumericValue(val, opt, validateType) {
const [item] = val;
const { type, value, unit } = item ?? {};
if (!validateType(type, value, unit)) {
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,
unit: unit ? asciiLowercase(unit) : null,
type
};
}
/**
* Serializes a <number> value.
*
* @param {Array<object>} val - The AST value.
* @param {object} [opt={}] - The options for parsing.
* @returns {string|undefined} The parsed number.
*/
function serializeNumber(val, opt = {}) {
const res = parseNumericValue(val, opt, (type) => type === AST_TYPES.NUMBER);
if (!res) {
return;
}
return `${res.num}`;
}
/**
* Serializes an <angle> value.
*
* @param {Array<object>} val - The AST value.
* @param {object} [opt={}] - The options for parsing.
* @returns {string|undefined} The serialized angle.
*/
function serializeAngle(val, opt = {}) {
const res = parseNumericValue(
val,
opt,
(type, value) => type === AST_TYPES.DIMENSION || (type === AST_TYPES.NUMBER && value === "0")
);
if (!res) {
return;
}
const { num, unit } = res;
if (unit) {
if (!/^(?:deg|g?rad|turn)$/i.test(unit)) {
return;
}
return `${num}${unit}`;
} else if (num === 0) {
return `${num}deg`;
}
}
/**
* Serializes a <length> value.
*
* @param {Array<object>} val - The AST value.
* @param {object} [opt={}] - The options for parsing.
* @returns {string|undefined} The serialized length.
*/
function serializeLength(val, opt = {}) {
const res = parseNumericValue(
val,
opt,
(type, value) => type === AST_TYPES.DIMENSION || (type === AST_TYPES.NUMBER && value === "0")
);
if (!res) {
return;
}
const { num, unit } = res;
if (num === 0 && !unit) {
return `${num}px`;
} else if (unit) {
return `${num}${unit}`;
}
}
/**
* Serializes a <dimension> value, e.g. <frequency>, <time> and <resolution>.
*
* @param {Array<object>} val - The AST value.
* @param {object} [opt={}] - The options for parsing.
* @returns {string|undefined} The serialized dimension.
*/
function serializeDimension(val, opt = {}) {
const res = parseNumericValue(val, opt, (type) => type === AST_TYPES.DIMENSION);
if (!res) {
return;
}
const { num, unit } = res;
if (unit) {
return `${num}${unit}`;
}
}
/**
* Serializes a <percentage> value.
*
* @param {Array<object>} val - The AST value.
* @param {object} [opt={}] - The options for parsing.
* @returns {string|undefined} The serialized percentage.
*/
function serializePercentage(val, opt = {}) {
const res = parseNumericValue(
val,
opt,
(type, value) => type === AST_TYPES.PERCENTAGE || (type === AST_TYPES.NUMBER && value === "0")
);
if (!res) {
return;
}
const { num } = res;
return `${num}%`;
}
/**
* Serializes a <url> value.
*
* @param {Array<object>} val - The AST value.
* @returns {string|undefined} The serialized url.
*/
function serializeURL(val) {
const [item] = val;
const { type, value } = item ?? {};
if (type !== AST_TYPES.URL) {
return;
}
const str = value.replace(/\\\\/g, "\\").replaceAll('"', '\\"');
return `url("${str}")`;
}
/**
* Serializes a <string> value.
*
* @param {Array<object>} val - The AST value.
* @returns {string|undefined} The serialized string.
*/
function serializeString(val) {
const [item] = val;
const { type, value } = item ?? {};
if (type !== AST_TYPES.STRING) {
return;
}
const str = value.replace(/\\\\/g, "\\").replaceAll('"', '\\"');
return `"${str}"`;
}
/**
* Serializes a <color> value.
*
* @param {Array<object>} val - The AST value.
* @returns {string|undefined} The serialized color.
*/
function serializeColor(val) {
const [item] = val;
const { name, type, value } = item ?? {};
switch (type) {
case AST_TYPES.FUNCTION: {
const res = resolveColor(`${name}(${value})`, {
format: "specifiedValue"
});
if (res) {
return res;
}
break;
}
case AST_TYPES.HASH: {
const res = resolveColor(`#${value}`, {
format: "specifiedValue"
});
if (res) {
return res;
}
break;
}
case AST_TYPES.IDENTIFIER: {
if (SYS_COLORS.has(name)) {
return name;
}
const res = resolveColor(name, {
format: "specifiedValue"
});
if (res) {
return res;
}
break;
}
default:
}
}
/**
* Serializes a <gradient> value.
*
* @param {Array<object>} val - The AST value.
* @returns {string|undefined} The serialized gradient.
*/
function serializeGradient(val) {
const [item] = val;
const { name, type, value } = item ?? {};
if (type !== AST_TYPES.FUNCTION) {
return;
}
const res = resolveGradient(`${name}(${value})`, {
format: "specifiedValue"
});
if (res) {
return res;
}
}
/**
* Resolves a keyword value.
*
* @param {Array<object>} value - The AST node array containing the keyword value.
* @param {object} [opt={}] - The options for parsing.
* @returns {string|undefined} The resolved keyword or undefined.
*/
function resolveKeywordValue(value, opt = {}) {
const [{ name, type }] = value;
const { length } = opt;
switch (type) {
case AST_TYPES.GLOBAL_KEYWORD: {
if (length > 1) {
return;
}
return name;
}
case AST_TYPES.IDENTIFIER: {
return name;
}
default:
}
}
/**
* Resolves a function value.
*
* @param {Array<object>} value - The AST node array containing the function value.
* @param {object} [opt={}] - The options for parsing.
* @returns {string|undefined} The resolved function or undefined.
*/
function resolveFunctionValue(value, opt = {}) {
const [{ name, type, value: itemValue }] = value;
const { length } = opt;
switch (type) {
case AST_TYPES.FUNCTION: {
return `${name}(${itemValue})`;
}
case AST_TYPES.GLOBAL_KEYWORD: {
if (length > 1) {
return;
}
return name;
}
case AST_TYPES.IDENTIFIER: {
return name;
}
default:
}
}
/**
* Resolves a numeric value.
*
* @param {Array<object>} value - The AST node array containing the numeric value.
* @param {object} [opt={}] - The options for parsing.
* @returns {string|undefined} The resolved length/percentage/number or undefined.
*/
function resolveNumericValue(value, opt = {}) {
const [{ name, type: itemType, value: itemValue }] = value;
const { length, type } = opt;
switch (itemType) {
case AST_TYPES.CALC: {
return `${name}(${itemValue})`;
}
case AST_TYPES.DIMENSION: {
if (type === "angle") {
return serializeAngle(value, opt);
} else if (type === "length") {
return serializeLength(value, opt);
}
return serializeDimension(value, opt);
}
case AST_TYPES.GLOBAL_KEYWORD: {
if (length > 1) {
return;
}
return name;
}
case AST_TYPES.IDENTIFIER: {
return name;
}
case AST_TYPES.NUMBER: {
switch (type) {
case "angle": {
return serializeAngle(value, opt);
}
case "length": {
return serializeLength(value, opt);
}
case "percentage": {
return serializePercentage(value, opt);
}
default: {
return serializeNumber(value, opt);
}
}
}
case AST_TYPES.PERCENTAGE: {
return serializePercentage(value, opt);
}
default:
}
}
/**
* Resolves a color value.
*
* @param {Array<object>} value - The AST node array containing the color value.
* @param {object} [opt={}] - The options for parsing.
* @returns {string|undefined} The resolved color or undefined.
*/
function resolveColorValue(value, opt = {}) {
const [{ name, type }] = value;
const { length } = opt;
switch (type) {
case AST_TYPES.GLOBAL_KEYWORD: {
if (length > 1) {
return;
}
return name;
}
default: {
return serializeColor(value, opt);
}
}
}
/**
* Resolves an image value.
*
* @param {Array<object>} value - The AST node array containing the image value.
* @param {object} [opt={}] - The options for parsing.
* @returns {string|undefined} The resolved gradient/url or undefined.
*/
function resolveImageValue(value, opt = {}) {
const [{ name, type }] = value;
const { length } = opt;
switch (type) {
case AST_TYPES.GLOBAL_KEYWORD: {
if (length > 1) {
return;
}
return name;
}
case AST_TYPES.IDENTIFIER: {
return name;
}
case AST_TYPES.URL: {
return serializeURL(value, opt);
}
default: {
return serializeGradient(value, opt);
}
}
}
/**
* Resolves a border shorthand value.
*
* @param {Array<object>} value - The AST node array containing the shorthand value.
* @param {object} subProps - The sub properties object.
* @param {Map} parsedValues - The Map of parsed values.
* @returns {Array|string|undefined} - The resolved [prop, value] pair, keyword or undefined.
*/
function resolveBorderShorthandValue(value, subProps, parsedValues) {
const [{ isNumber, name, type, value: itemValue }] = value;
const { color: colorProp, style: styleProp, width: widthProp } = subProps;
switch (type) {
case AST_TYPES.CALC: {
if (isNumber || parsedValues.has(widthProp)) {
return;
}
return [widthProp, `${name}(${itemValue}`];
}
case AST_TYPES.DIMENSION:
case AST_TYPES.NUMBER: {
if (parsedValues.has(widthProp)) {
return;
}
const parsedValue = serializeLength(value, { min: 0 });
if (!parsedValue) {
return;
}
return [widthProp, parsedValue];
}
case AST_TYPES.FUNCTION:
case AST_TYPES.HASH: {
if (parsedValues.has(colorProp)) {
return;
}
const parsedValue = serializeColor(value);
if (!parsedValue) {
return;
}
return [colorProp, parsedValue];
}
case AST_TYPES.GLOBAL_KEYWORD: {
return name;
}
case AST_TYPES.IDENTIFIER: {
if (isValidPropertyValue(widthProp, name)) {
if (parsedValues.has(widthProp)) {
return;
}
return [widthProp, name];
} else if (isValidPropertyValue(styleProp, name)) {
if (parsedValues.has(styleProp)) {
return;
}
return [styleProp, name];
} else if (isValidPropertyValue(colorProp, name)) {
if (parsedValues.has(colorProp)) {
return;
}
return [colorProp, name];
}
break;
}
default:
}
}
module.exports = {
AST_TYPES,
hasCalcFunc,
hasVarFunc,
isGlobalKeyword,
isValidPropertyValue,
parseCSS,
parsePropertyValue,
prepareValue,
resolveBorderShorthandValue,
resolveCalc,
resolveColorValue,
resolveFunctionValue,
resolveImageValue,
resolveKeywordValue,
resolveNumericValue,
serializeAngle,
serializeColor,
serializeDimension,
serializeGradient,
serializeLength,
serializeNumber,
serializePercentage,
serializeString,
serializeURL,
splitValue
};
+405
View File
@@ -0,0 +1,405 @@
"use strict";
const parsers = require("../parsers");
const backgroundImage = require("./backgroundImage");
const backgroundPosition = require("./backgroundPosition");
const backgroundSize = require("./backgroundSize");
const backgroundRepeat = require("./backgroundRepeat");
const backgroundOrigin = require("./backgroundOrigin");
const backgroundClip = require("./backgroundClip");
const backgroundAttachment = require("./backgroundAttachment");
const backgroundColor = require("./backgroundColor");
const property = "background";
const initialValues = new Map([
[backgroundImage.property, "none"],
[backgroundPosition.property, "0% 0%"],
[backgroundSize.property, "auto"],
[backgroundRepeat.property, "repeat"],
[backgroundOrigin.property, "padding-box"],
[backgroundClip.property, "border-box"],
[backgroundAttachment.property, "scroll"],
[backgroundColor.property, "transparent"]
]);
const shorthandFor = new Map([
[backgroundImage.property, backgroundImage],
[backgroundPosition.property, backgroundPosition],
[backgroundSize.property, backgroundSize],
[backgroundRepeat.property, backgroundRepeat],
[backgroundOrigin.property, backgroundOrigin],
[backgroundClip.property, backgroundClip],
[backgroundAttachment.property, backgroundAttachment],
[backgroundColor.property, backgroundColor]
]);
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (v === "" || parsers.hasVarFunc(v)) {
for (const [key] of shorthandFor) {
this._setProperty(key, "");
}
this._setProperty(property, v);
} else {
const bgValues = parse(v);
if (!Array.isArray(bgValues)) {
return;
}
const bgMap = new Map([
[backgroundImage.property, []],
[backgroundPosition.property, []],
[backgroundSize.property, []],
[backgroundRepeat.property, []],
[backgroundOrigin.property, []],
[backgroundClip.property, []],
[backgroundAttachment.property, []],
[backgroundColor.property, []]
]);
const backgrounds = [];
for (const bgValue of bgValues) {
const bg = [];
for (const [longhand, value] of Object.entries(bgValue)) {
if (value) {
const arr = bgMap.get(longhand);
arr.push(value);
bgMap.set(longhand, arr);
if (value !== initialValues.get(longhand)) {
if (longhand === backgroundSize.property) {
bg.push(`/ ${value}`);
} else {
bg.push(value);
}
} else if (longhand === backgroundImage.property) {
if (v === "none") {
bg.push(value);
}
} else if (longhand === backgroundColor.property) {
if (v === "transparent") {
bg.push(value);
}
}
}
}
backgrounds.push(bg.join(" "));
}
const priority = this._priorities.get(property) ?? "";
for (const [longhand, value] of bgMap) {
this._setProperty(longhand, value.join(", "), priority);
}
this._setProperty(property, backgrounds.join(", "), priority);
}
},
get() {
const v = this.getPropertyValue(property);
if (parsers.hasVarFunc(v)) {
return v;
}
const bgMap = new Map();
let l = 0;
for (const [longhand] of shorthandFor) {
const val = this.getPropertyValue(longhand);
if (longhand === backgroundImage.property) {
if (val === "none" && v === "none" && this.getPropertyValue(backgroundColor.property) === "transparent") {
return val;
}
if (val !== initialValues.get(longhand)) {
const imgValues = parsers.splitValue(val, {
delimiter: ","
});
l = imgValues.length;
bgMap.set(longhand, imgValues);
}
} else if (longhand === backgroundColor.property) {
if (val !== initialValues.get(longhand) || v.includes(val)) {
bgMap.set(longhand, [val]);
}
} else if (val !== initialValues.get(longhand)) {
bgMap.set(
longhand,
parsers.splitValue(val, {
delimiter: ","
})
);
}
}
if (l === 0) {
const bgColArr = bgMap.get(backgroundColor.property);
const background = bgColArr ? bgColArr[0] : null;
if (background) {
return background;
}
return "";
}
const bgValues = [];
for (let i = 0; i < l; i++) {
bgValues[i] = [];
}
for (const [longhand, values] of bgMap) {
for (let i = 0; i < l; i++) {
switch (longhand) {
case backgroundColor.property: {
if (i === l - 1) {
const value = values[0];
if (parsers.hasVarFunc(value)) {
return "";
}
if (value && value !== initialValues.get(longhand)) {
const bgValue = bgValues[i];
bgValue.push(value);
}
}
break;
}
case backgroundSize.property: {
const value = values[i];
if (parsers.hasVarFunc(value)) {
return "";
}
if (value && value !== initialValues.get(longhand)) {
const bgValue = bgValues[i];
bgValue.push(`/ ${value}`);
}
break;
}
default: {
const value = values[i];
if (parsers.hasVarFunc(value)) {
return "";
}
if (value && value !== initialValues.get(longhand)) {
const bgValue = bgValues[i];
bgValue.push(value);
}
}
}
}
}
const backgrounds = [];
for (const bgValue of bgValues) {
backgrounds.push(bgValue.join(" "));
}
return backgrounds.join(", ");
},
enumerable: true,
configurable: true
};
/**
* Parses the background property value.
*
* @param {string} v - The value to parse.
* @returns {Array<object>|undefined} The parsed background values or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
} else if (parsers.hasCalcFunc(v)) {
v = parsers.resolveCalc(v);
}
if (!parsers.isValidPropertyValue(property, v)) {
return;
}
const values = parsers.splitValue(v, {
delimiter: ","
});
const bgValues = [];
const l = values.length;
for (let i = 0; i < l; i++) {
let bg = {
[backgroundImage.property]: initialValues.get(backgroundImage.property),
[backgroundPosition.property]: initialValues.get(backgroundPosition.property),
[backgroundSize.property]: initialValues.get(backgroundSize.property),
[backgroundRepeat.property]: initialValues.get(backgroundRepeat.property),
[backgroundOrigin.property]: initialValues.get(backgroundOrigin.property),
[backgroundClip.property]: initialValues.get(backgroundClip.property),
[backgroundAttachment.property]: initialValues.get(backgroundAttachment.property),
[backgroundColor.property]: initialValues.get(backgroundColor.property)
};
if (l > 1 && i !== l - 1) {
bg = {
[backgroundImage.property]: initialValues.get(backgroundImage.property),
[backgroundPosition.property]: initialValues.get(backgroundPosition.property),
[backgroundSize.property]: initialValues.get(backgroundSize.property),
[backgroundRepeat.property]: initialValues.get(backgroundRepeat.property),
[backgroundOrigin.property]: initialValues.get(backgroundOrigin.property),
[backgroundClip.property]: initialValues.get(backgroundClip.property),
[backgroundAttachment.property]: initialValues.get(backgroundAttachment.property)
};
}
const bgPosition = [];
const bgSize = [];
const bgRepeat = [];
const bgBox = [];
const bgParts = parsers.splitValue(values[i], {
delimiter: "/"
});
if (!bgParts.length || bgParts.length > 2) {
return;
}
const [bgPart1, bgPart2 = ""] = bgParts;
const parts1 = parsers.splitValue(bgPart1);
for (const part of parts1) {
let partValid = false;
for (const [longhand, value] of shorthandFor) {
if (parsers.isValidPropertyValue(longhand, part)) {
partValid = true;
switch (longhand) {
case backgroundClip.property:
case backgroundOrigin.property: {
const parsedValue = value.parse(part);
if (parsedValue) {
bgBox.push(parsedValue);
}
break;
}
case backgroundColor.property: {
if (i !== values.length - 1) {
return;
}
const parsedValue = value.parse(part);
if (parsedValue) {
bg[longhand] = parsedValue;
}
break;
}
case backgroundPosition.property: {
const parsedValue = value.parse(part);
if (parsedValue) {
bgPosition.push(parsedValue);
}
break;
}
case backgroundRepeat.property: {
const parsedValue = value.parse(part);
if (parsedValue) {
bgRepeat.push(parsedValue);
}
break;
}
case backgroundSize.property: {
break;
}
default: {
const parsedValue = value.parse(part);
if (parsedValue) {
bg[longhand] = parsedValue;
}
}
}
}
}
if (!partValid) {
return;
}
}
if (bgPart2) {
const parts2 = parsers.splitValue(bgPart2);
for (const part of parts2) {
let partValid = false;
for (const [longhand, value] of shorthandFor) {
if (parsers.isValidPropertyValue(longhand, part)) {
partValid = true;
switch (longhand) {
case backgroundClip.property:
case backgroundOrigin.property: {
const parsedValue = value.parse(part);
if (parsedValue) {
bgBox.push(parsedValue);
}
break;
}
case backgroundColor.property: {
if (i !== l - 1) {
return;
}
const parsedValue = value.parse(part);
if (parsedValue) {
bg[longhand] = parsedValue;
}
break;
}
case backgroundPosition.property: {
break;
}
case backgroundRepeat.property: {
const parsedValue = value.parse(part);
if (parsedValue) {
bgRepeat.push(parsedValue);
}
break;
}
case backgroundSize.property: {
const parsedValue = value.parse(part);
if (parsedValue) {
bgSize.push(parsedValue);
}
break;
}
default: {
const parsedValue = value.parse(part);
if (parsedValue) {
bg[longhand] = parsedValue;
}
}
}
}
}
if (!partValid) {
return;
}
}
}
if (bgPosition.length) {
const { parse: parser } = shorthandFor.get(backgroundPosition.property);
const value = parser(bgPosition.join(" "));
if (value) {
bg[backgroundPosition.property] = value;
}
}
if (bgSize.length) {
const { parse: parser } = shorthandFor.get(backgroundSize.property);
const value = parser(bgSize.join(" "));
if (value) {
bg[backgroundSize.property] = value;
}
}
if (bgRepeat.length) {
const { parse: parser } = shorthandFor.get(backgroundRepeat.property);
const value = parser(bgRepeat.join(" "));
if (value) {
bg[backgroundRepeat.property] = value;
}
}
if (bgBox.length) {
switch (bgBox.length) {
case 1: {
const [value] = bgBox;
bg[backgroundOrigin.property] = value;
bg[backgroundClip.property] = value;
break;
}
case 2: {
const [value1, value2] = bgBox;
bg[backgroundOrigin.property] = value1;
bg[backgroundClip.property] = value2;
break;
}
default: {
return;
}
}
}
bgValues.push(bg);
}
return bgValues;
}
module.exports = {
descriptor,
initialValues,
parse,
property,
shorthandFor
};
+63
View File
@@ -0,0 +1,63 @@
"use strict";
const parsers = require("../parsers");
const property = "background-attachment";
const shorthand = "background";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the background-attachment property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const values = parsers.splitValue(v, { delimiter: "," });
const parsedValues = [];
for (const val of values) {
const value = parsers.parsePropertyValue(property, val);
if (Array.isArray(value) && value.length === 1) {
const parsedValue = parsers.resolveKeywordValue(value);
if (!parsedValue) {
return;
}
parsedValues.push(parsedValue);
} else if (typeof value === "string") {
parsedValues.push(value);
}
}
if (parsedValues.length) {
return parsedValues.join(", ");
}
}
module.exports = {
descriptor,
parse,
property
};
+63
View File
@@ -0,0 +1,63 @@
"use strict";
const parsers = require("../parsers");
const property = "background-clip";
const shorthand = "background";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the background-clip property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const values = parsers.splitValue(v, { delimiter: "," });
const parsedValues = [];
for (const val of values) {
const value = parsers.parsePropertyValue(property, val);
if (Array.isArray(value) && value.length === 1) {
const parsedValue = parsers.resolveKeywordValue(value);
if (!parsedValue) {
return;
}
parsedValues.push(parsedValue);
} else if (typeof value === "string") {
parsedValues.push(value);
}
}
if (parsedValues.length) {
return parsedValues.join(", ");
}
}
module.exports = {
descriptor,
parse,
property
};
+52
View File
@@ -0,0 +1,52 @@
"use strict";
const parsers = require("../parsers");
const property = "background-color";
const shorthand = "background";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the background-color property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveColorValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+65
View File
@@ -0,0 +1,65 @@
"use strict";
const parsers = require("../parsers");
const property = "background-image";
const shorthand = "background";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the background-image property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const values = parsers.splitValue(v, { delimiter: "," });
const parsedValues = [];
for (const val of values) {
const value = parsers.parsePropertyValue(property, val);
if (Array.isArray(value) && value.length === 1) {
const parsedValue = parsers.resolveImageValue(value);
if (!parsedValue) {
return;
}
parsedValues.push(parsedValue);
} else if (typeof value === "string") {
parsedValues.push(value);
} else {
return;
}
}
if (parsedValues.length) {
return parsedValues.join(", ");
}
}
module.exports = {
descriptor,
parse,
property
};
+63
View File
@@ -0,0 +1,63 @@
"use strict";
const parsers = require("../parsers");
const property = "background-origin";
const shorthand = "background";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the background-origin property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const values = parsers.splitValue(v, { delimiter: "," });
const parsedValues = [];
for (const val of values) {
const value = parsers.parsePropertyValue(property, val);
if (Array.isArray(value) && value.length === 1) {
const parsedValue = parsers.resolveKeywordValue(value);
if (!parsedValue) {
return;
}
parsedValues.push(parsedValue);
} else if (typeof value === "string") {
parsedValues.push(value);
}
}
if (parsedValues.length) {
return parsedValues.join(", ");
}
}
module.exports = {
descriptor,
parse,
property
};
+204
View File
@@ -0,0 +1,204 @@
"use strict";
const parsers = require("../parsers");
// Constants
const { AST_TYPES } = parsers;
const property = "background-position";
const shorthand = "background";
const keyX = ["left", "right"];
const keyY = ["top", "bottom"];
const keywordsX = ["center", ...keyX];
const keywordsY = ["center", ...keyY];
const keywords = ["center", ...keyX, ...keyY];
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the background-position property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const values = parsers.splitValue(v, {
delimiter: ","
});
const parsedValues = [];
for (const val of values) {
const value = parsers.parsePropertyValue(property, val);
if (Array.isArray(value) && value.length) {
const [part1, part2, part3, part4] = value;
let parsedValue = "";
switch (value.length) {
case 1: {
if (part1.type === AST_TYPES.GLOBAL_KEYWORD) {
parsedValue = part1.name;
} else {
const val1 =
part1.type === AST_TYPES.IDENTIFIER
? part1.name
: parsers.resolveNumericValue([part1], { type: "length" });
if (val1) {
if (val1 === "center") {
parsedValue = `${val1} ${val1}`;
} else if (val1 === "top" || val1 === "bottom") {
parsedValue = `center ${val1}`;
} else {
parsedValue = `${val1} center`;
}
}
}
break;
}
case 2: {
const val1 =
part1.type === AST_TYPES.IDENTIFIER ? part1.name : parsers.resolveNumericValue([part1], { type: "length" });
const val2 =
part2.type === AST_TYPES.IDENTIFIER ? part2.name : parsers.resolveNumericValue([part2], { type: "length" });
if (val1 && val2) {
if (keywordsX.includes(val1) && keywordsY.includes(val2)) {
parsedValue = `${val1} ${val2}`;
} 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}`;
}
}
break;
}
case 3: {
const val1 = part1.type === AST_TYPES.IDENTIFIER && part1.name;
const val2 =
part2.type === AST_TYPES.IDENTIFIER ? part2.name : parsers.resolveNumericValue([part2], { type: "length" });
const val3 =
part3.type === AST_TYPES.IDENTIFIER ? part3.name : parsers.resolveNumericValue([part3], { type: "length" });
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 (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}`;
}
}
}
break;
}
case 4: {
const val1 = part1.type === AST_TYPES.IDENTIFIER && part1.name;
const val2 = parsers.resolveNumericValue([part2], { type: "length" });
const val3 = part3.type === AST_TYPES.IDENTIFIER && part3.name;
const val4 = parsers.resolveNumericValue([part4], { type: "length" });
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;
}
default:
}
if (parsedValue) {
parsedValues.push(parsedValue);
} else {
return;
}
} else if (typeof value === "string") {
parsedValues.push(value);
}
}
if (parsedValues.length) {
return parsedValues.join(", ");
}
}
module.exports = {
descriptor,
parse,
property
};
+96
View File
@@ -0,0 +1,96 @@
"use strict";
const parsers = require("../parsers");
// Constants
const { AST_TYPES } = parsers;
const property = "background-repeat";
const shorthand = "background";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the background-repeat property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const values = parsers.splitValue(v, {
delimiter: ","
});
const parsedValues = [];
for (const val of values) {
const value = parsers.parsePropertyValue(property, val);
if (Array.isArray(value) && value.length) {
let parsedValue = "";
switch (value.length) {
case 1: {
const [part1] = value;
if (part1.type === AST_TYPES.GLOBAL_KEYWORD || part1.type === AST_TYPES.IDENTIFIER) {
parsedValue = part1.name;
}
break;
}
case 2: {
const [part1, part2] = value;
const val1 = part1.type === AST_TYPES.IDENTIFIER && part1.name;
const val2 = part2.type === AST_TYPES.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;
}
} else if (typeof value === "string") {
parsedValues.push(value);
}
}
if (parsedValues.length) {
return parsedValues.join(", ");
}
}
module.exports = {
descriptor,
parse,
property
};
+130
View File
@@ -0,0 +1,130 @@
"use strict";
const parsers = require("../parsers");
// Constants
const { AST_TYPES } = parsers;
const property = "background-size";
const shorthand = "background";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the background-size property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const values = parsers.splitValue(v, {
delimiter: ","
});
const parsedValues = [];
for (const val of values) {
const value = parsers.parsePropertyValue(property, val);
if (Array.isArray(value) && value.length) {
if (value.length === 1) {
const [{ isNumber, name, type, value: itemValue }] = value;
switch (type) {
case AST_TYPES.CALC: {
if (isNumber) {
return;
}
parsedValues.push(`${name}(${itemValue})`);
break;
}
case AST_TYPES.GLOBAL_KEYWORD:
case AST_TYPES.IDENTIFIER: {
parsedValues.push(name);
break;
}
default: {
const parsedValue = parsers.resolveNumericValue(value, {
type: "length"
});
if (!parsedValue) {
return;
}
parsedValues.push(parsedValue);
}
}
} else {
const [val1, val2] = value;
const parts = [];
if (val1.type === AST_TYPES.CALC && !val1.isNumber) {
parts.push(`${val1.name}(${val1.value})`);
} else if (val1.type === AST_TYPES.IDENTIFIER) {
parts.push(val1.name);
} else if (val1.type === AST_TYPES.DIMENSION) {
parts.push(`${val1.value}${val1.unit}`);
} else if (val1.type === AST_TYPES.PERCENTAGE) {
parts.push(`${val1.value}%`);
} else {
return;
}
switch (val2.type) {
case AST_TYPES.CALC: {
if (val2.isNumber) {
return;
}
parts.push(`${val2.name}(${val2.value})`);
break;
}
case AST_TYPES.DIMENSION: {
parts.push(`${val2.value}${val2.unit}`);
break;
}
case AST_TYPES.IDENTIFIER: {
if (val2.name !== "auto") {
parts.push(val2.name);
}
break;
}
case AST_TYPES.PERCENTAGE: {
parts.push(`${val2.value}%`);
break;
}
default: {
return;
}
}
parsedValues.push(parts.join(" "));
}
} else if (typeof value === "string") {
parsedValues.push(value);
}
}
if (parsedValues.length) {
return parsedValues.join(", ");
}
}
module.exports = {
descriptor,
parse,
property
};
+114
View File
@@ -0,0 +1,114 @@
"use strict";
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 property = "border";
const subProps = {
width: borderWidth.property,
style: borderStyle.property,
color: borderColor.property
};
const initialValues = new Map([
[borderWidth.property, "medium"],
[borderStyle.property, "none"],
[borderColor.property, "currentcolor"]
]);
const shorthandFor = new Map([
[borderWidth.property, borderWidth],
[borderStyle.property, borderStyle],
[borderColor.property, borderColor]
]);
const positionShorthandFor = new Map([
[borderTop.property, borderTop],
[borderRight.property, borderRight],
[borderBottom.property, borderBottom],
[borderLeft.property, borderLeft]
]);
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._borderSetter(property, v, "");
} else {
const val = parse(v);
if (val || typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._borderSetter(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border property value.
*
* @param {string} v - The value to parse.
* @returns {object|string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "" || parsers.hasVarFunc(v)) {
return v;
}
const values = parsers.splitValue(v);
const parsedValues = new Map();
for (const val of values) {
const value = parsers.parsePropertyValue(property, val);
if (Array.isArray(value) && value.length === 1) {
const parsedValue = parsers.resolveBorderShorthandValue(value, subProps, parsedValues);
if (typeof parsedValue === "string") {
return parsedValue;
} else if (Array.isArray(parsedValue)) {
const [key, resolvedVal] = parsedValue;
parsedValues.set(key, resolvedVal);
} else {
return;
}
} else {
return;
}
}
if (parsedValues.size) {
const keys = shorthandFor.keys();
const obj = {
[borderWidth.property]: "medium"
};
for (const key of keys) {
if (parsedValues.has(key)) {
const parsedValue = parsedValues.get(key);
if (parsedValue !== initialValues.get(key)) {
obj[key] = parsedValues.get(key);
if (obj[borderWidth.property] && obj[borderWidth.property] === "medium") {
delete obj[borderWidth.property];
}
}
}
}
return obj;
}
}
module.exports = {
descriptor,
initialValues,
parse,
positionShorthandFor,
property,
shorthandFor
};
+49
View File
@@ -0,0 +1,49 @@
"use strict";
const parsers = require("../parsers");
const property = "border-block-end-color";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-block-end-color property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveColorValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+49
View File
@@ -0,0 +1,49 @@
"use strict";
const parsers = require("../parsers");
const property = "border-block-start-color";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-block-start-color property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveColorValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+104
View File
@@ -0,0 +1,104 @@
"use strict";
const parsers = require("../parsers");
const borderBottomWidth = require("./borderBottomWidth");
const borderBottomStyle = require("./borderBottomStyle");
const borderBottomColor = require("./borderBottomColor");
const property = "border-bottom";
const shorthand = "border";
const subProps = {
width: borderBottomWidth.property,
style: borderBottomStyle.property,
color: borderBottomColor.property
};
const initialValues = new Map([
[borderBottomWidth.property, "medium"],
[borderBottomStyle.property, "none"],
[borderBottomColor.property, "currentcolor"]
]);
const shorthandFor = new Map([
[borderBottomWidth.property, borderBottomWidth],
[borderBottomStyle.property, borderBottomStyle],
[borderBottomColor.property, borderBottomColor]
]);
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._borderSetter(property, v, "");
} else {
const val = parse(v);
if (val || typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._borderSetter(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-bottom property value.
*
* @param {string} v - The value to parse.
* @returns {object|string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const values = parsers.splitValue(v);
const parsedValues = new Map();
for (const val of values) {
const value = parsers.parsePropertyValue(property, val);
if (Array.isArray(value) && value.length === 1) {
const parsedValue = parsers.resolveBorderShorthandValue(value, subProps, parsedValues);
if (typeof parsedValue === "string") {
return parsedValue;
} else if (Array.isArray(parsedValue)) {
const [key, resolvedVal] = parsedValue;
parsedValues.set(key, resolvedVal);
} else {
return;
}
} else {
return;
}
}
if (parsedValues.size) {
const keys = shorthandFor.keys();
const obj = {
[borderBottomWidth.property]: "medium"
};
for (const key of keys) {
if (parsedValues.has(key)) {
const parsedValue = parsedValues.get(key);
if (parsedValue !== initialValues.get(key)) {
obj[key] = parsedValues.get(key);
if (obj[borderBottomWidth.property] && obj[borderBottomWidth.property] === "medium") {
delete obj[borderBottomWidth.property];
}
}
}
}
return obj;
}
}
module.exports = {
descriptor,
initialValues,
parse,
property,
shorthandFor
};
+58
View File
@@ -0,0 +1,58 @@
"use strict";
const parsers = require("../parsers");
const property = "border-bottom-color";
const lineShorthand = "border-color";
const positionShorthand = "border-bottom";
const shorthand = "border";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._borderSetter(property, v, "");
} else {
const val = parse(v);
if (typeof val === "string") {
const shorthandPriority = this._priorities.get(shorthand);
const linePriority = this._priorities.get(lineShorthand);
const positionPriority = this._priorities.get(positionShorthand);
const priority =
!(shorthandPriority || linePriority || positionPriority) && this._priorities.has(property)
? this._priorities.get(property)
: "";
this._borderSetter(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-bottom-color property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveColorValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+58
View File
@@ -0,0 +1,58 @@
"use strict";
const parsers = require("../parsers");
const property = "border-bottom-style";
const lineShorthand = "border-style";
const positionShorthand = "border-bottom";
const shorthand = "border";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._borderSetter(property, v, "");
} else {
const val = parse(v);
if (typeof val === "string") {
const shorthandPriority = this._priorities.get(shorthand);
const linePriority = this._priorities.get(lineShorthand);
const positionPriority = this._priorities.get(positionShorthand);
const priority =
!(shorthandPriority || linePriority || positionPriority) && this._priorities.has(property)
? this._priorities.get(property)
: "";
this._borderSetter(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-bottom-style property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveKeywordValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+61
View File
@@ -0,0 +1,61 @@
"use strict";
const parsers = require("../parsers");
const property = "border-bottom-width";
const lineShorthand = "border-width";
const positionShorthand = "border-bottom";
const shorthand = "border";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._borderSetter(property, v, "");
} else {
const val = parse(v);
if (typeof val === "string") {
const shorthandPriority = this._priorities.get(shorthand);
const linePriority = this._priorities.get(lineShorthand);
const positionPriority = this._priorities.get(positionShorthand);
const priority =
!(shorthandPriority || linePriority || positionPriority) && this._priorities.has(property)
? this._priorities.get(property)
: "";
this._borderSetter(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-bottom-width property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
min: 0,
type: "length"
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+49
View File
@@ -0,0 +1,49 @@
"use strict";
const parsers = require("../parsers");
const property = "border-collapse";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-collapse property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveKeywordValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+113
View File
@@ -0,0 +1,113 @@
"use strict";
const parsers = require("../parsers");
const borderTopColor = require("./borderTopColor");
const borderRightColor = require("./borderRightColor");
const borderBottomColor = require("./borderBottomColor");
const borderLeftColor = require("./borderLeftColor");
const property = "border-color";
const shorthand = "border";
const shorthandFor = new Map([
[borderTopColor.property, borderTopColor],
[borderRightColor.property, borderRightColor],
[borderBottomColor.property, borderBottomColor],
[borderLeftColor.property, borderLeftColor]
]);
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._borderSetter(property, v, "");
} else {
const val = parse(v);
if (Array.isArray(val) || typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._borderSetter(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-color property value.
*
* @param {string} v - The value to parse.
* @returns {Array<string>|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const values = parsers.parsePropertyValue(property, v);
const parsedValues = [];
if (Array.isArray(values) && values.length) {
if (values.length > 4) {
return;
}
for (const value of values) {
const parsedValue = parsers.resolveColorValue([value], {
length: values.length
});
if (!parsedValue) {
return;
}
parsedValues.push(parsedValue);
}
} else if (typeof values === "string") {
parsedValues.push(values);
}
if (parsedValues.length) {
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:
}
}
}
module.exports = {
descriptor,
parse,
property,
shorthandFor
};
+49
View File
@@ -0,0 +1,49 @@
"use strict";
const parsers = require("../parsers");
const property = "border-inline-end-color";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-inline-end-color property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveColorValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+49
View File
@@ -0,0 +1,49 @@
"use strict";
const parsers = require("../parsers");
const property = "border-inline-start-color";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-inline-start-color property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveColorValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+104
View File
@@ -0,0 +1,104 @@
"use strict";
const parsers = require("../parsers");
const borderLeftWidth = require("./borderLeftWidth");
const borderLeftStyle = require("./borderLeftStyle");
const borderLeftColor = require("./borderLeftColor");
const property = "border-left";
const shorthand = "border";
const subProps = {
width: borderLeftWidth.property,
style: borderLeftStyle.property,
color: borderLeftColor.property
};
const initialValues = new Map([
[borderLeftWidth.property, "medium"],
[borderLeftStyle.property, "none"],
[borderLeftColor.property, "currentcolor"]
]);
const shorthandFor = new Map([
[borderLeftWidth.property, borderLeftWidth],
[borderLeftStyle.property, borderLeftStyle],
[borderLeftColor.property, borderLeftColor]
]);
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._borderSetter(property, v, "");
} else {
const val = parse(v);
if (val || typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._borderSetter(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-left property value.
*
* @param {string} v - The value to parse.
* @returns {object|string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const values = parsers.splitValue(v);
const parsedValues = new Map();
for (const val of values) {
const value = parsers.parsePropertyValue(property, val);
if (Array.isArray(value) && value.length === 1) {
const parsedValue = parsers.resolveBorderShorthandValue(value, subProps, parsedValues);
if (typeof parsedValue === "string") {
return parsedValue;
} else if (Array.isArray(parsedValue)) {
const [key, resolvedVal] = parsedValue;
parsedValues.set(key, resolvedVal);
} else {
return;
}
} else {
return;
}
}
if (parsedValues.size) {
const keys = shorthandFor.keys();
const obj = {
[borderLeftWidth.property]: "medium"
};
for (const key of keys) {
if (parsedValues.has(key)) {
const parsedValue = parsedValues.get(key);
if (parsedValue !== initialValues.get(key)) {
obj[key] = parsedValues.get(key);
if (obj[borderLeftWidth.property] && obj[borderLeftWidth.property] === "medium") {
delete obj[borderLeftWidth.property];
}
}
}
}
return obj;
}
}
module.exports = {
descriptor,
initialValues,
parse,
property,
shorthandFor
};
+58
View File
@@ -0,0 +1,58 @@
"use strict";
const parsers = require("../parsers");
const property = "border-left-color";
const lineShorthand = "border-color";
const positionShorthand = "border-left";
const shorthand = "border";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._borderSetter(property, v, "");
} else {
const val = parse(v);
if (typeof val === "string") {
const shorthandPriority = this._priorities.get(shorthand);
const linePriority = this._priorities.get(lineShorthand);
const positionPriority = this._priorities.get(positionShorthand);
const priority =
!(shorthandPriority || linePriority || positionPriority) && this._priorities.has(property)
? this._priorities.get(property)
: "";
this._borderSetter(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-left-color property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveColorValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+58
View File
@@ -0,0 +1,58 @@
"use strict";
const parsers = require("../parsers");
const property = "border-left-style";
const lineShorthand = "border-style";
const positionShorthand = "border-left";
const shorthand = "border";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._borderSetter(property, v, "");
} else {
const val = parse(v);
if (typeof val === "string") {
const shorthandPriority = this._priorities.get(shorthand);
const linePriority = this._priorities.get(lineShorthand);
const positionPriority = this._priorities.get(positionShorthand);
const priority =
!(shorthandPriority || linePriority || positionPriority) && this._priorities.has(property)
? this._priorities.get(property)
: "";
this._borderSetter(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-left-style property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveKeywordValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+61
View File
@@ -0,0 +1,61 @@
"use strict";
const parsers = require("../parsers");
const property = "border-left-width";
const lineShorthand = "border-width";
const positionShorthand = "border-left";
const shorthand = "border";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._borderSetter(property, v, "");
} else {
const val = parse(v);
if (typeof val === "string") {
const shorthandPriority = this._priorities.get(shorthand);
const linePriority = this._priorities.get(lineShorthand);
const positionPriority = this._priorities.get(positionShorthand);
const priority =
!(shorthandPriority || linePriority || positionPriority) && this._priorities.has(property)
? this._priorities.get(property)
: "";
this._borderSetter(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-left-width property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
min: 0,
type: "length"
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+104
View File
@@ -0,0 +1,104 @@
"use strict";
const parsers = require("../parsers");
const borderRightWidth = require("./borderRightWidth");
const borderRightStyle = require("./borderRightStyle");
const borderRightColor = require("./borderRightColor");
const property = "border-right";
const shorthand = "border";
const subProps = {
width: borderRightWidth.property,
style: borderRightStyle.property,
color: borderRightColor.property
};
const initialValues = new Map([
[borderRightWidth.property, "medium"],
[borderRightStyle.property, "none"],
[borderRightColor.property, "currentcolor"]
]);
const shorthandFor = new Map([
[borderRightWidth.property, borderRightWidth],
[borderRightStyle.property, borderRightStyle],
[borderRightColor.property, borderRightColor]
]);
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._borderSetter(property, v, "");
} else {
const val = parse(v);
if (val || typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._borderSetter(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-right property value.
*
* @param {string} v - The value to parse.
* @returns {object|string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const values = parsers.splitValue(v);
const parsedValues = new Map();
for (const val of values) {
const value = parsers.parsePropertyValue(property, val);
if (Array.isArray(value) && value.length === 1) {
const parsedValue = parsers.resolveBorderShorthandValue(value, subProps, parsedValues);
if (typeof parsedValue === "string") {
return parsedValue;
} else if (Array.isArray(parsedValue)) {
const [key, resolvedVal] = parsedValue;
parsedValues.set(key, resolvedVal);
} else {
return;
}
} else {
return;
}
}
if (parsedValues.size) {
const keys = shorthandFor.keys();
const obj = {
[borderRightWidth.property]: "medium"
};
for (const key of keys) {
if (parsedValues.has(key)) {
const parsedValue = parsedValues.get(key);
if (parsedValue !== initialValues.get(key)) {
obj[key] = parsedValues.get(key);
if (obj[borderRightWidth.property] && obj[borderRightWidth.property] === "medium") {
delete obj[borderRightWidth.property];
}
}
}
}
return obj;
}
}
module.exports = {
descriptor,
initialValues,
parse,
property,
shorthandFor
};
+58
View File
@@ -0,0 +1,58 @@
"use strict";
const parsers = require("../parsers");
const property = "border-right-color";
const lineShorthand = "border-color";
const positionShorthand = "border-right";
const shorthand = "border";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._borderSetter(property, v, "");
} else {
const val = parse(v);
if (typeof val === "string") {
const shorthandPriority = this._priorities.get(shorthand);
const linePriority = this._priorities.get(lineShorthand);
const positionPriority = this._priorities.get(positionShorthand);
const priority =
!(shorthandPriority || linePriority || positionPriority) && this._priorities.has(property)
? this._priorities.get(property)
: "";
this._borderSetter(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-right-color property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveColorValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+58
View File
@@ -0,0 +1,58 @@
"use strict";
const parsers = require("../parsers");
const property = "border-right-style";
const lineShorthand = "border-style";
const positionShorthand = "border-right";
const shorthand = "border";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._borderSetter(property, v, "");
} else {
const val = parse(v);
if (typeof val === "string") {
const shorthandPriority = this._priorities.get(shorthand);
const linePriority = this._priorities.get(lineShorthand);
const positionPriority = this._priorities.get(positionShorthand);
const priority =
!(shorthandPriority || linePriority || positionPriority) && this._priorities.has(property)
? this._priorities.get(property)
: "";
this._borderSetter(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-right-style property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveKeywordValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+61
View File
@@ -0,0 +1,61 @@
"use strict";
const parsers = require("../parsers");
const property = "border-right-width";
const lineShorthand = "border-width";
const positionShorthand = "border-right";
const shorthand = "border";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._borderSetter(property, v, "");
} else {
const val = parse(v);
if (typeof val === "string") {
const shorthandPriority = this._priorities.get(shorthand);
const linePriority = this._priorities.get(lineShorthand);
const positionPriority = this._priorities.get(positionShorthand);
const priority =
!(shorthandPriority || linePriority || positionPriority) && this._priorities.has(property)
? this._priorities.get(property)
: "";
this._borderSetter(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-right-width property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
min: 0,
type: "length"
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+69
View File
@@ -0,0 +1,69 @@
"use strict";
const parsers = require("../parsers");
const property = "border-spacing";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-spacing property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length) {
switch (value.length) {
case 1: {
return parsers.resolveNumericValue(value, {
type: "length"
});
}
case 2: {
const [part1, part2] = value;
const val1 = parsers.resolveNumericValue([part1], {
type: "length"
});
const val2 = parsers.resolveNumericValue([part2], {
type: "length"
});
if (val1 && val2) {
return `${val1} ${val2}`;
}
break;
}
default:
}
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+113
View File
@@ -0,0 +1,113 @@
"use strict";
const parsers = require("../parsers");
const borderTopStyle = require("./borderTopStyle");
const borderRightStyle = require("./borderRightStyle");
const borderBottomStyle = require("./borderBottomStyle");
const borderLeftStyle = require("./borderLeftStyle");
const property = "border-style";
const shorthand = "border";
const shorthandFor = new Map([
[borderTopStyle.property, borderTopStyle],
[borderRightStyle.property, borderRightStyle],
[borderBottomStyle.property, borderBottomStyle],
[borderLeftStyle.property, borderLeftStyle]
]);
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._borderSetter(property, v, "");
} else {
const val = parse(v);
if (Array.isArray(val) || typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._borderSetter(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-style property value.
*
* @param {string} v - The value to parse.
* @returns {Array<string>|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const values = parsers.parsePropertyValue(property, v);
const parsedValues = [];
if (Array.isArray(values) && values.length) {
if (values.length > 4) {
return;
}
for (const value of values) {
const parsedValue = parsers.resolveKeywordValue([value], {
length: values.length
});
if (!parsedValue) {
return;
}
parsedValues.push(parsedValue);
}
} else if (typeof values === "string") {
parsedValues.push(values);
}
if (parsedValues.length) {
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:
}
}
}
module.exports = {
descriptor,
parse,
property,
shorthandFor
};
+104
View File
@@ -0,0 +1,104 @@
"use strict";
const parsers = require("../parsers");
const borderTopWidth = require("./borderTopWidth");
const borderTopStyle = require("./borderTopStyle");
const borderTopColor = require("./borderTopColor");
const property = "border-top";
const shorthand = "border";
const subProps = {
width: borderTopWidth.property,
style: borderTopStyle.property,
color: borderTopColor.property
};
const initialValues = new Map([
[borderTopWidth.property, "medium"],
[borderTopStyle.property, "none"],
[borderTopColor.property, "currentcolor"]
]);
const shorthandFor = new Map([
[borderTopWidth.property, borderTopWidth],
[borderTopStyle.property, borderTopStyle],
[borderTopColor.property, borderTopColor]
]);
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._borderSetter(property, v, "");
} else {
const val = parse(v);
if (val || typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._borderSetter(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-top property value.
*
* @param {string} v - The value to parse.
* @returns {object|string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const values = parsers.splitValue(v);
const parsedValues = new Map();
for (const val of values) {
const value = parsers.parsePropertyValue(property, val);
if (Array.isArray(value) && value.length === 1) {
const parsedValue = parsers.resolveBorderShorthandValue(value, subProps, parsedValues);
if (typeof parsedValue === "string") {
return parsedValue;
} else if (Array.isArray(parsedValue)) {
const [key, resolvedVal] = parsedValue;
parsedValues.set(key, resolvedVal);
} else {
return;
}
} else {
return;
}
}
if (parsedValues.size) {
const keys = shorthandFor.keys();
const obj = {
[borderTopWidth.property]: "medium"
};
for (const key of keys) {
if (parsedValues.has(key)) {
const parsedValue = parsedValues.get(key);
if (parsedValue !== initialValues.get(key)) {
obj[key] = parsedValues.get(key);
if (obj[borderTopWidth.property] && obj[borderTopWidth.property] === "medium") {
delete obj[borderTopWidth.property];
}
}
}
}
return obj;
}
}
module.exports = {
descriptor,
initialValues,
parse,
property,
shorthandFor
};
+58
View File
@@ -0,0 +1,58 @@
"use strict";
const parsers = require("../parsers");
const property = "border-top-color";
const lineShorthand = "border-color";
const positionShorthand = "border-top";
const shorthand = "border";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._borderSetter(property, v, "");
} else {
const val = parse(v);
if (typeof val === "string") {
const shorthandPriority = this._priorities.get(shorthand);
const linePriority = this._priorities.get(lineShorthand);
const positionPriority = this._priorities.get(positionShorthand);
const priority =
!(shorthandPriority || linePriority || positionPriority) && this._priorities.has(property)
? this._priorities.get(property)
: "";
this._borderSetter(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-top-color property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveColorValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+58
View File
@@ -0,0 +1,58 @@
"use strict";
const parsers = require("../parsers");
const property = "border-top-style";
const lineShorthand = "border-style";
const positionShorthand = "border-top";
const shorthand = "border";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._borderSetter(property, v, "");
} else {
const val = parse(v);
if (typeof val === "string") {
const shorthandPriority = this._priorities.get(shorthand);
const linePriority = this._priorities.get(lineShorthand);
const positionPriority = this._priorities.get(positionShorthand);
const priority =
!(shorthandPriority || linePriority || positionPriority) && this._priorities.has(property)
? this._priorities.get(property)
: "";
this._borderSetter(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-top-style property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveKeywordValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+61
View File
@@ -0,0 +1,61 @@
"use strict";
const parsers = require("../parsers");
const property = "border-top-width";
const lineShorthand = "border-width";
const positionShorthand = "border-top";
const shorthand = "border";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._borderSetter(property, v, "");
} else {
const val = parse(v);
if (typeof val === "string") {
const shorthandPriority = this._priorities.get(shorthand);
const linePriority = this._priorities.get(lineShorthand);
const positionPriority = this._priorities.get(positionShorthand);
const priority =
!(shorthandPriority || linePriority || positionPriority) && this._priorities.has(property)
? this._priorities.get(property)
: "";
this._borderSetter(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-top-width property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
min: 0,
type: "length"
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+114
View File
@@ -0,0 +1,114 @@
"use strict";
const parsers = require("../parsers");
const borderTopWidth = require("./borderTopWidth");
const borderRightWidth = require("./borderRightWidth");
const borderBottomWidth = require("./borderBottomWidth");
const borderLeftWidth = require("./borderLeftWidth");
const property = "border-width";
const shorthand = "border";
const shorthandFor = new Map([
[borderTopWidth.property, borderTopWidth],
[borderRightWidth.property, borderRightWidth],
[borderBottomWidth.property, borderBottomWidth],
[borderLeftWidth.property, borderLeftWidth]
]);
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._borderSetter(property, v, "");
} else {
const val = parse(v);
if (Array.isArray(val) || typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._borderSetter(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the border-width property value.
*
* @param {string} v - The value to parse.
* @returns {Array<string>|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const values = parsers.parsePropertyValue(property, v);
const parsedValues = [];
if (Array.isArray(values) && values.length) {
if (values.length > 4) {
return;
}
for (const value of values) {
const parsedValue = parsers.resolveNumericValue([value], {
length: values.length,
type: "length"
});
if (!parsedValue) {
return;
}
parsedValues.push(parsedValue);
}
} else if (typeof values === "string") {
parsedValues.push(values);
}
if (parsedValues.length) {
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:
}
}
}
module.exports = {
descriptor,
parse,
property,
shorthandFor
};
+51
View File
@@ -0,0 +1,51 @@
"use strict";
const parsers = require("../parsers");
const property = "bottom";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the bottom property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
type: "length"
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+49
View File
@@ -0,0 +1,49 @@
"use strict";
const parsers = require("../parsers");
const property = "clear";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the clear property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveKeywordValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+79
View File
@@ -0,0 +1,79 @@
"use strict";
// deprecated
// @see https://drafts.csswg.org/css-masking-1/#clip-property
const parsers = require("../parsers");
// Constants
const { AST_TYPES } = parsers;
const property = "clip";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the clip property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
const [{ name, type, value: itemValue }] = value;
switch (type) {
case AST_TYPES.FUNCTION: {
const values = parsers.splitValue(itemValue, {
delimiter: ","
});
const parsedValues = [];
for (const item of values) {
const parsedValue = parsers.parseCSS(item, { context: "value" });
const val = parsers.resolveNumericValue(parsedValue.children, {
type: "length"
});
if (val) {
parsedValues.push(val);
} else {
return;
}
}
return `${name}(${parsedValues.join(", ")})`;
}
case AST_TYPES.GLOBAL_KEYWORD:
case AST_TYPES.IDENTIFIER: {
return name;
}
default:
}
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+49
View File
@@ -0,0 +1,49 @@
"use strict";
const parsers = require("../parsers");
const property = "color";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the color property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveColorValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+216
View File
@@ -0,0 +1,216 @@
"use strict";
const parsers = require("../parsers");
// Constants
const { AST_TYPES } = parsers;
const property = "display";
/* keywords */
const displayOutside = ["block", "inline", "run-in"];
const displayFlow = ["flow", "flow-root"];
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the display property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length) {
switch (value.length) {
case 1: {
const [{ name, type }] = value;
switch (type) {
case AST_TYPES.GLOBAL_KEYWORD: {
return name;
}
case AST_TYPES.IDENTIFIER: {
if (name === "flow") {
return "block";
}
return name;
}
default:
}
break;
}
case 2: {
const [part1, part2] = value;
const val1 = part1.type === AST_TYPES.IDENTIFIER && part1.name;
const val2 = part2.type === AST_TYPES.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;
}
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:
}
}
}
break;
}
case 3: {
const [part1, part2, part3] = value;
const val1 = part1.type === AST_TYPES.IDENTIFIER && part1.name;
const val2 = part2.type === AST_TYPES.IDENTIFIER && part2.name;
const val3 = part3.type === AST_TYPES.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;
}
}
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}`;
}
}
}
}
break;
}
default:
}
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+186
View File
@@ -0,0 +1,186 @@
"use strict";
const parsers = require("../parsers");
const flexGrow = require("./flexGrow");
const flexShrink = require("./flexShrink");
const flexBasis = require("./flexBasis");
// Constants
const { AST_TYPES } = parsers;
const property = "flex";
const initialValues = new Map([
[flexGrow.property, "0"],
[flexShrink.property, "1"],
[flexBasis.property, "auto"]
]);
const shorthandFor = new Map([
[flexGrow.property, flexGrow],
[flexShrink.property, flexShrink],
[flexBasis.property, flexBasis]
]);
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
for (const [longhand] of shorthandFor) {
this._setProperty(longhand, "");
}
this._setProperty(property, v);
} else {
const val = parse(v);
const priority = this._priorities.get(property) ?? "";
if (typeof val === "string") {
for (const [longhand] of shorthandFor) {
this._setProperty(longhand, val, priority);
}
this._setProperty(property, val, priority);
} else if (val) {
const values = [];
for (const [longhand, value] of Object.entries(val)) {
values.push(value);
this._setProperty(longhand, value, priority);
}
this._setProperty(property, values.join(" "), priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the flex property value.
*
* @param {string} v - The value to parse.
* @returns {object|string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length) {
const flex = {
[flexGrow.property]: "1",
[flexShrink.property]: "1",
[flexBasis.property]: "0%"
};
if (value.length === 1) {
const [{ isNumber, name, type, unit, value: itemValue }] = value;
switch (type) {
case AST_TYPES.CALC: {
if (isNumber) {
flex[flexGrow.property] = `${name}(${itemValue})`;
return flex;
}
flex[flexBasis.property] = `${name}(${itemValue})`;
return flex;
}
case AST_TYPES.DIMENSION: {
flex[flexBasis.property] = `${itemValue}${unit}`;
return flex;
}
case AST_TYPES.GLOBAL_KEYWORD: {
return name;
}
case AST_TYPES.IDENTIFIER: {
if (name === "none") {
return {
[flexGrow.property]: "0",
[flexShrink.property]: "0",
[flexBasis.property]: "auto"
};
}
flex[flexBasis.property] = name;
return flex;
}
case AST_TYPES.NUMBER: {
flex[flexGrow.property] = itemValue;
return flex;
}
case AST_TYPES.PERCENTAGE: {
flex[flexBasis.property] = `${itemValue}%`;
return flex;
}
default:
}
} else {
const [val1, val2, val3] = value;
if (val1.type === AST_TYPES.CALC && val1.isNumber) {
flex[flexGrow.property] = `${val1.name}(${val1.value})`;
} else if (val1.type === AST_TYPES.NUMBER) {
flex[flexGrow.property] = val1.value;
} else {
return;
}
if (val3) {
if (val2.type === AST_TYPES.CALC && val2.isNumber) {
flex[flexShrink.property] = `${val2.name}(${val2.value})`;
} else if (val2.type === AST_TYPES.NUMBER) {
flex[flexShrink.property] = val2.value;
} else {
return;
}
if (val3.type === AST_TYPES.GLOBAL_KEYWORD || val3.type === AST_TYPES.IDENTIFIER) {
flex[flexBasis.property] = val3.name;
} else if (val3.type === AST_TYPES.CALC && !val3.isNumber) {
flex[flexBasis.property] = `${val3.name}(${val3.value})`;
} else if (val3.type === AST_TYPES.DIMENSION) {
flex[flexBasis.property] = `${val3.value}${val3.unit}`;
} else if (val3.type === AST_TYPES.PERCENTAGE) {
flex[flexBasis.property] = `${val3.value}%`;
} else {
return;
}
} else {
switch (val2.type) {
case AST_TYPES.CALC: {
if (val2.isNumber) {
flex[flexShrink.property] = `${val2.name}(${val2.value})`;
} else {
flex[flexBasis.property] = `${val2.name}(${val2.value})`;
}
break;
}
case AST_TYPES.DIMENSION: {
flex[flexBasis.property] = `${val2.value}${val2.unit}`;
break;
}
case AST_TYPES.NUMBER: {
flex[flexShrink.property] = val2.value;
break;
}
case AST_TYPES.PERCENTAGE: {
flex[flexBasis.property] = `${val2.value}%`;
break;
}
case AST_TYPES.IDENTIFIER: {
flex[flexBasis.property] = val2.name;
break;
}
default: {
return;
}
}
}
return flex;
}
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
initialValues,
parse,
property,
shorthandFor
};
+54
View File
@@ -0,0 +1,54 @@
"use strict";
const parsers = require("../parsers");
const property = "flex-basis";
const shorthand = "flex";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._flexBoxSetter(property, val, priority, shorthand);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the flex-basis property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
type: "length"
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+54
View File
@@ -0,0 +1,54 @@
"use strict";
const parsers = require("../parsers");
const property = "flex-grow";
const shorthand = "flex";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._flexBoxSetter(property, val, priority, shorthand);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the flex-grow property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue("flex-grow", v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
min: 0
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+54
View File
@@ -0,0 +1,54 @@
"use strict";
const parsers = require("../parsers");
const property = "flex-shrink";
const shorthand = "flex";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._flexBoxSetter(property, val, priority, shorthand);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the flex-shrink property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
min: 0
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+49
View File
@@ -0,0 +1,49 @@
"use strict";
const parsers = require("../parsers");
const property = "float";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the float property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveKeywordValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+49
View File
@@ -0,0 +1,49 @@
"use strict";
const parsers = require("../parsers");
const property = "flood-color";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the flood-color property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveColorValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+273
View File
@@ -0,0 +1,273 @@
"use strict";
const parsers = require("../parsers");
const fontStyle = require("./fontStyle");
const fontVariant = require("./fontVariant");
const fontWeight = require("./fontWeight");
const fontSize = require("./fontSize");
const lineHeight = require("./lineHeight");
const fontFamily = require("./fontFamily");
// Constants
const { AST_TYPES } = parsers;
const property = "font";
const shorthandFor = new Map([
[fontStyle.property, fontStyle],
[fontVariant.property, fontVariant],
[fontWeight.property, fontWeight],
[fontSize.property, fontSize],
[lineHeight.property, lineHeight],
[fontFamily.property, fontFamily]
]);
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (v === "" || parsers.hasVarFunc(v)) {
for (const [key] of shorthandFor) {
this._setProperty(key, "");
}
this._setProperty(property, v);
} else {
const obj = parse(v);
if (!obj) {
return;
}
const priority = this._priorities.get(property) ?? "";
const str = new Set();
for (const [key] of shorthandFor) {
const val = obj[key];
if (typeof val === "string") {
this._setProperty(key, val, priority);
if (val && val !== "normal" && !str.has(val)) {
if (key === lineHeight.property) {
str.add(`/ ${val}`);
} else {
str.add(val);
}
}
}
}
this._setProperty(property, [...str].join(" "), priority);
}
},
get() {
const val = this.getPropertyValue(property);
if (parsers.hasVarFunc(val)) {
return val;
}
const str = new Set();
for (const [key] of shorthandFor) {
const v = this.getPropertyValue(key);
if (parsers.hasVarFunc(v)) {
return "";
}
if (v && v !== "normal" && !str.has(v)) {
if (key === lineHeight.property) {
str.add(`/ ${v}`);
} else {
str.add(`${v}`);
}
}
}
return [...str].join(" ");
},
enumerable: true,
configurable: true
};
/**
* Parses the font property value.
*
* @param {string} v - The value to parse.
* @returns {object|undefined} The parsed value object or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
} else if (parsers.hasCalcFunc(v)) {
v = parsers.resolveCalc(v);
}
if (!parsers.isValidPropertyValue(property, v)) {
return;
}
const [fontBlock, ...families] = parsers.splitValue(v, {
delimiter: ","
});
const [fontBlockA, fontBlockB] = parsers.splitValue(fontBlock, {
delimiter: "/"
});
const font = {
[fontStyle.property]: "normal",
[fontVariant.property]: "normal",
[fontWeight.property]: "normal"
};
const fontFamilies = new Set();
if (fontBlockB) {
const [lineB, ...familiesB] = fontBlockB.trim().split(" ");
if (!lineB || !familiesB.length) {
return;
}
const lineHeightB = lineHeight.parse(lineB);
if (typeof lineHeightB !== "string") {
return;
}
const familyB = fontFamily.parse(familiesB.join(" "));
if (typeof familyB === "string") {
fontFamilies.add(familyB);
} else {
return;
}
const parts = parsers.splitValue(fontBlockA.trim());
const properties = [fontStyle.property, fontVariant.property, fontWeight.property, fontSize.property];
for (const part of parts) {
if (part === "normal") {
continue;
} else {
for (const longhand of properties) {
switch (longhand) {
case fontSize.property: {
const parsedValue = fontSize.parse(part);
if (typeof parsedValue === "string") {
font[longhand] = parsedValue;
}
break;
}
case fontStyle.property:
case fontWeight.property: {
if (font[longhand] === "normal") {
const longhandItem = shorthandFor.get(longhand);
const parsedValue = longhandItem.parse(part);
if (typeof parsedValue === "string") {
font[longhand] = parsedValue;
}
}
break;
}
case fontVariant.property: {
if (font[longhand] === "normal") {
const parsedValue = fontVariant.parse(part);
if (typeof parsedValue === "string") {
if (parsedValue === "small-cap") {
font[longhand] = parsedValue;
} else if (parsedValue !== "normal") {
return;
}
}
}
break;
}
default:
}
}
}
}
if (Object.hasOwn(font, fontSize.property)) {
font[lineHeight.property] = lineHeightB;
} else {
return;
}
} else {
const revParts = parsers.splitValue(fontBlockA.trim()).toReversed();
if (revParts.length === 1) {
const [part] = revParts;
const value = parsers.parsePropertyValue(property, part);
if (Array.isArray(value) && value.length === 1) {
const [{ name, type }] = value;
if (type === AST_TYPES.GLOBAL_KEYWORD) {
return {
[fontStyle.property]: name,
[fontVariant.property]: name,
[fontWeight.property]: name,
[fontSize.property]: name,
[lineHeight.property]: name,
[fontFamily.property]: name
};
}
}
return;
}
const properties = [fontStyle.property, fontVariant.property, fontWeight.property, lineHeight.property];
for (const longhand of properties) {
font[longhand] = "normal";
}
const revFontFamily = [];
let fontSizeA;
for (const part of revParts) {
if (fontSizeA) {
if (/^normal$/i.test(part)) {
continue;
} else {
for (const longhand of properties) {
switch (longhand) {
case fontStyle.property:
case fontWeight.property:
case lineHeight.property: {
if (font[longhand] === "normal") {
const longhandItem = shorthandFor.get(longhand);
const parsedValue = longhandItem.parse(part);
if (typeof parsedValue === "string") {
font[longhand] = parsedValue;
}
}
break;
}
case fontVariant.property: {
if (font[longhand] === "normal") {
const parsedValue = fontVariant.parse(part);
if (typeof parsedValue === "string") {
if (parsedValue === "small-cap") {
font[longhand] = parsedValue;
} else if (parsedValue !== "normal") {
return;
}
}
}
break;
}
default:
}
}
}
} else {
const parsedFontSize = fontSize.parse(part);
if (typeof parsedFontSize === "string") {
fontSizeA = parsedFontSize;
} else {
const parsedFontFamily = fontFamily.parse(part);
if (typeof parsedFontFamily === "string") {
revFontFamily.push(parsedFontFamily);
} else {
return;
}
}
}
}
const family = fontFamily.parse(revFontFamily.toReversed().join(" "));
if (fontSizeA && family) {
font[fontSize.property] = fontSizeA;
fontFamilies.add(fontFamily.parse(family));
} else {
return;
}
}
for (const family of families) {
const parsedFontFamily = fontFamily.parse(family);
if (parsedFontFamily) {
fontFamilies.add(parsedFontFamily);
} else {
return;
}
}
font[fontFamily.property] = [...fontFamilies].join(", ");
return font;
}
module.exports = {
descriptor,
parse,
property,
shorthandFor
};
+106
View File
@@ -0,0 +1,106 @@
"use strict";
const parsers = require("../parsers");
// Constants
const { AST_TYPES } = parsers;
const property = "font-family";
const shorthand = "font";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the font-family property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const values = parsers.splitValue(v, {
delimiter: ","
});
const parsedValues = [];
for (const val of values) {
if (!val) {
return "";
}
const value = parsers.parsePropertyValue(property, val, {
caseSensitive: true
});
if (Array.isArray(value) && value.length) {
if (value.length === 1) {
const [{ name, type, value: itemValue }] = value;
switch (type) {
case AST_TYPES.FUNCTION: {
parsedValues.push(`${name}(${itemValue})`);
break;
}
case AST_TYPES.GLOBAL_KEYWORD:
case AST_TYPES.IDENTIFIER: {
if (name === "undefined") {
return;
}
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 !== AST_TYPES.IDENTIFIER) {
return;
}
parts.push(name);
}
const parsedValue = parts.join(" ").replaceAll("\\", "").replaceAll('"', '\\"');
parsedValues.push(`"${parsedValue}"`);
}
} else if (typeof value === "string") {
parsedValues.push(value);
} else {
return;
}
}
if (parsedValues.length) {
return parsedValues.join(", ");
}
}
module.exports = {
descriptor,
parse,
property
};
+55
View File
@@ -0,0 +1,55 @@
"use strict";
const parsers = require("../parsers");
const property = "font-size";
const shorthand = "font";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the font-size property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
min: 0,
type: "length"
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+73
View File
@@ -0,0 +1,73 @@
"use strict";
const parsers = require("../parsers");
// Constants
const { AST_TYPES } = parsers;
const property = "font-style";
const shorthand = "font";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the font-style property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length) {
if (value.length === 1) {
const [{ name, type }] = value;
switch (type) {
case AST_TYPES.GLOBAL_KEYWORD:
case AST_TYPES.IDENTIFIER: {
return name;
}
default:
}
} else if (value.length === 2) {
const [part1, part2] = value;
const val1 = part1.type === AST_TYPES.IDENTIFIER && part1.name;
const val2 = parsers.resolveNumericValue([part2], {
type: "angle"
});
if (val1 && val1 === "oblique" && val2) {
return `${val1} ${val2}`;
}
}
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+68
View File
@@ -0,0 +1,68 @@
"use strict";
const parsers = require("../parsers");
const property = "font-variant";
const shorthand = "font";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the font-variant property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const values = parsers.splitValue(v);
const parsedValues = [];
for (const val of values) {
const value = parsers.parsePropertyValue(property, val);
if (Array.isArray(value) && value.length === 1) {
const parsedValue = parsers.resolveFunctionValue(value);
if (!parsedValue) {
return;
}
parsedValues.push(parsedValue);
} 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(" ");
}
}
module.exports = {
descriptor,
parse,
property
};
+59
View File
@@ -0,0 +1,59 @@
"use strict";
const parsers = require("../parsers");
const property = "font-weight";
const shorthand = "font";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the font-weight property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
const parsedValue = parsers.resolveNumericValue(value, {
min: 1,
max: 1000
});
if (!parsedValue) {
return;
}
return parsedValue;
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+52
View File
@@ -0,0 +1,52 @@
"use strict";
const parsers = require("../parsers");
const property = "height";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the height property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
min: 0,
type: "length"
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+51
View File
@@ -0,0 +1,51 @@
"use strict";
const parsers = require("../parsers");
const property = "left";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the left property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
type: "length"
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+49
View File
@@ -0,0 +1,49 @@
"use strict";
const parsers = require("../parsers");
const property = "lighting-color";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the lighting-color property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveColorValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+54
View File
@@ -0,0 +1,54 @@
"use strict";
const parsers = require("../parsers");
const property = "line-height";
const shorthand = "font";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the line-height property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
min: 0
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+83
View File
@@ -0,0 +1,83 @@
"use strict";
const parsers = require("../parsers");
const marginTop = require("./marginTop");
const marginRight = require("./marginRight");
const marginBottom = require("./marginBottom");
const marginLeft = require("./marginLeft");
const property = "margin";
const position = "edges";
const shorthandFor = new Map([
[marginTop.property, marginTop],
[marginRight.property, marginRight],
[marginBottom.property, marginBottom],
[marginLeft.property, marginLeft]
]);
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
for (const [longhand] of shorthandFor) {
this._setProperty(longhand, "");
}
this._setProperty(property, v);
} else {
const val = parse(v);
if (Array.isArray(val) || typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._positionShorthandSetter(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the margin property value.
*
* @param {string} v - The value to parse.
* @returns {Array<string>|string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const values = parsers.parsePropertyValue(property, v);
const parsedValues = [];
if (Array.isArray(values) && values.length) {
if (values.length > 4) {
return;
}
for (const value of values) {
const parsedValue = parsers.resolveNumericValue([value], {
length: values.length,
type: "length"
});
if (!parsedValue) {
return;
}
parsedValues.push(parsedValue);
}
} else if (typeof values === "string") {
parsedValues.push(values);
}
if (parsedValues.length) {
return parsedValues;
}
}
module.exports = {
descriptor,
parse,
position,
property,
shorthandFor
};
+57
View File
@@ -0,0 +1,57 @@
"use strict";
const parsers = require("../parsers");
const property = "margin-bottom";
const shorthand = "margin";
const position = "bottom";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._positionLonghandSetter(property, val, priority, shorthand);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the margin-bottom property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
type: "length"
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
position,
property
};
+57
View File
@@ -0,0 +1,57 @@
"use strict";
const parsers = require("../parsers");
const property = "margin-left";
const shorthand = "margin";
const position = "left";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._positionLonghandSetter(property, val, priority, shorthand);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the margin-left property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
type: "length"
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
position,
property
};
+57
View File
@@ -0,0 +1,57 @@
"use strict";
const parsers = require("../parsers");
const property = "margin-right";
const shorthand = "margin";
const position = "right";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._positionLonghandSetter(property, val, priority, shorthand);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the margin-right property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
type: "length"
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
position,
property
};
+57
View File
@@ -0,0 +1,57 @@
"use strict";
const parsers = require("../parsers");
const property = "margin-top";
const shorthand = "margin";
const position = "top";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._positionLonghandSetter(property, val, priority, shorthand);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the margin-top property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
type: "length"
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
position,
property
};
+51
View File
@@ -0,0 +1,51 @@
"use strict";
const parsers = require("../parsers");
const property = "opacity";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the opacity property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
clamp: true
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+49
View File
@@ -0,0 +1,49 @@
"use strict";
const parsers = require("../parsers");
const property = "outline-color";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the outline-color property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveColorValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+84
View File
@@ -0,0 +1,84 @@
"use strict";
const parsers = require("../parsers");
const paddingTop = require("./paddingTop");
const paddingRight = require("./paddingRight");
const paddingBottom = require("./paddingBottom");
const paddingLeft = require("./paddingLeft");
const property = "padding";
const position = "edges";
const shorthandFor = new Map([
[paddingTop.property, paddingTop],
[paddingRight.property, paddingRight],
[paddingBottom.property, paddingBottom],
[paddingLeft.property, paddingLeft]
]);
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
for (const [longhand] of shorthandFor) {
this._setProperty(longhand, "");
}
this._setProperty(property, v);
} else {
const val = parse(v);
if (Array.isArray(val) || typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._positionShorthandSetter(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the padding property value.
*
* @param {string} v - The value to parse.
* @returns {Array<string>|string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const values = parsers.parsePropertyValue(property, v);
const parsedValues = [];
if (Array.isArray(values) && values.length) {
if (values.length > 4) {
return;
}
for (const value of values) {
const parsedValue = parsers.resolveNumericValue([value], {
length: values.length,
min: 0,
type: "length"
});
if (!parsedValue) {
return;
}
parsedValues.push(parsedValue);
}
} else if (typeof values === "string") {
parsedValues.push(values);
}
if (parsedValues.length) {
return parsedValues;
}
}
module.exports = {
descriptor,
parse,
position,
property,
shorthandFor
};
+58
View File
@@ -0,0 +1,58 @@
"use strict";
const parsers = require("../parsers");
const property = "padding-bottom";
const shorthand = "padding";
const position = "bottom";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._positionLonghandSetter(property, val, priority, shorthand);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the padding-bottom property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
min: 0,
type: "length"
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
position,
property
};
+58
View File
@@ -0,0 +1,58 @@
"use strict";
const parsers = require("../parsers");
const property = "padding-left";
const shorthand = "padding";
const position = "left";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._positionLonghandSetter(property, val, priority, shorthand);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the padding-left property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
min: 0,
type: "length"
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
position,
property
};
+58
View File
@@ -0,0 +1,58 @@
"use strict";
const parsers = require("../parsers");
const property = "padding-right";
const shorthand = "padding";
const position = "right";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._positionLonghandSetter(property, val, priority, shorthand);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the padding-right property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
min: 0,
type: "length"
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
position,
property
};
+58
View File
@@ -0,0 +1,58 @@
"use strict";
const parsers = require("../parsers");
const property = "padding-top";
const shorthand = "padding";
const position = "top";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(shorthand, "");
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority =
!this._priorities.get(shorthand) && this._priorities.has(property) ? this._priorities.get(property) : "";
this._positionLonghandSetter(property, val, priority, shorthand);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the padding-top property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
min: 0,
type: "length"
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
position,
property
};
+51
View File
@@ -0,0 +1,51 @@
"use strict";
const parsers = require("../parsers");
const property = "right";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the right property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
type: "length"
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+49
View File
@@ -0,0 +1,49 @@
"use strict";
const parsers = require("../parsers");
const property = "stop-color";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the stop-color property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveColorValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+49
View File
@@ -0,0 +1,49 @@
"use strict";
const parsers = require("../parsers");
const property = "text-emphasis-color";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the text-emphasis-color property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveColorValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+51
View File
@@ -0,0 +1,51 @@
"use strict";
const parsers = require("../parsers");
const property = "top";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the top property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
type: "length"
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+49
View File
@@ -0,0 +1,49 @@
"use strict";
const parsers = require("../parsers");
const property = "-webkit-text-fill-color";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the -webkit-text-fill-color property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveColorValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+49
View File
@@ -0,0 +1,49 @@
"use strict";
const parsers = require("../parsers");
const property = "-webkit-text-stroke-color";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the -webkit-text-stroke-color property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveColorValue(value);
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+52
View File
@@ -0,0 +1,52 @@
"use strict";
const parsers = require("../parsers");
const property = "width";
const descriptor = {
set(v) {
v = parsers.prepareValue(v);
if (parsers.hasVarFunc(v)) {
this._setProperty(property, v);
} else {
const val = parse(v);
if (typeof val === "string") {
const priority = this._priorities.get(property) ?? "";
this._setProperty(property, val, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
/**
* Parses the width property value.
*
* @param {string} v - The value to parse.
* @returns {string|undefined} The parsed value or undefined if invalid.
*/
function parse(v) {
if (v === "") {
return v;
}
const value = parsers.parsePropertyValue(property, v);
if (Array.isArray(value) && value.length === 1) {
return parsers.resolveNumericValue(value, {
min: 0,
type: "length"
});
} else if (typeof value === "string") {
return value;
}
}
module.exports = {
descriptor,
parse,
property
};
+142
View File
@@ -0,0 +1,142 @@
"use strict";
const parsers = require("../parsers");
// Constants
const { AST_TYPES } = parsers;
/**
* Creates a generic property descriptor for a given property. Such descriptors are used whenever we don't have a
* specific handler in `./properties/*.js`. They perform some basic logic that works as a fallback, and is correct for
* simple properties, but properties with more complex grammars will need their own handlers.
*
* @param {string} property - The canonical CSS property name (e.g. "backdrop-filter", not "backdropFilter").
* @param {object} opts - The options object.
* @param {boolean} opts.caseSensitive - True if value is case-sensitive, false otherwise.
* @param {object} [opts.dimensionTypes={}] - An object containing information about the dimension types used by this
* property, if any. Keys are a type of dimension, which determines which serializer to use, and values are the
* information used by the serializer to serialize a parsed value.
* @param {object} [opts.functionTypes={}] - An object containing information about the function types used by this
* property, if any. Keys are a type of function, which determines which function to use; values are ignored.
* @returns {object} The property descriptor object.
*/
function createGenericPropertyDescriptor(property, { caseSensitive, dimensionTypes = {}, functionTypes = {} }) {
return {
set(v) {
const value = parsers.prepareValue(v);
if (parsers.hasVarFunc(value)) {
this._setProperty(property, value);
} else {
const parsedValue = parsers.parsePropertyValue(property, v, {
caseSensitive
});
const priority = this._priorities.get(property) ?? "";
if (Array.isArray(parsedValue)) {
if (parsedValue.length === 1) {
const {
angle: angleType,
dimension: dimensionType,
length: lengthType,
number: numberType,
percentage: percentageType
} = dimensionTypes;
const { color: colorType, image: imageType, paint: paintType } = functionTypes;
const [{ name, type, value: itemValue }] = parsedValue;
switch (type) {
case AST_TYPES.CALC: {
this._setProperty(property, `${name}(${itemValue})`, priority);
break;
}
case AST_TYPES.DIMENSION: {
let val;
if (dimensionType && lengthType) {
val = parsers.serializeLength(parsedValue, lengthType);
if (!val) {
val = parsers.serializeDimension(parsedValue, dimensionType);
}
} else if (lengthType) {
val = parsers.serializeLength(parsedValue, lengthType);
} else {
val = parsers.serializeDimension(parsedValue, dimensionType);
}
this._setProperty(property, val, priority);
break;
}
case AST_TYPES.HASH: {
this._setProperty(property, parsers.serializeColor(parsedValue), priority);
break;
}
case AST_TYPES.NUMBER: {
let val;
if (numberType) {
val = parsers.serializeNumber(parsedValue, numberType);
} else if (angleType) {
val = parsers.serializeAngle(parsedValue, angleType);
} else if (lengthType) {
val = parsers.serializeLength(parsedValue, lengthType);
} else if (percentageType) {
val = parsers.serializePercentage(parsedValue, percentageType);
}
this._setProperty(property, val, priority);
break;
}
case AST_TYPES.GLOBAL_KEYWORD:
case AST_TYPES.IDENTIFIER: {
this._setProperty(property, name, priority);
break;
}
case AST_TYPES.PERCENTAGE: {
let numericType;
if (percentageType) {
numericType = percentageType;
} else if (dimensionType) {
numericType = dimensionType;
} else if (angleType) {
numericType = angleType;
} else if (lengthType) {
numericType = lengthType;
}
if (numericType) {
this._setProperty(property, parsers.resolveNumericValue(parsedValue, numericType), priority);
}
break;
}
case AST_TYPES.STRING: {
this._setProperty(property, parsers.serializeString(parsedValue), priority);
break;
}
case AST_TYPES.URL: {
this._setProperty(property, parsers.serializeURL(parsedValue), priority);
break;
}
case AST_TYPES.FUNCTION:
default: {
if (colorType || paintType) {
this._setProperty(property, parsers.serializeColor(parsedValue), priority);
} else if (imageType) {
this._setProperty(property, parsers.serializeGradient(parsedValue), priority);
} else {
this._setProperty(property, value, priority);
}
}
}
} else {
// Set the prepared value for lists containing multiple values.
this._setProperty(property, value, priority);
}
} else if (typeof parsedValue === "string") {
this._setProperty(property, parsedValue, priority);
}
}
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
}
module.exports = {
createGenericPropertyDescriptor
};
+28
View File
@@ -0,0 +1,28 @@
// Forked from https://github.com/jsdom/jsdom/blob/main/lib/jsdom/living/helpers/strings.js
"use strict";
/**
* Converts a string to ASCII lowercase.
*
* @see https://infra.spec.whatwg.org/#ascii-lowercase
* @param {string} s - The string to convert.
* @returns {string} The converted string.
*/
function asciiLowercase(s) {
if (!/[^\x00-\x7f]/.test(s)) {
return s.toLowerCase();
}
const len = s.length;
const out = new Array(len);
for (let i = 0; i < len; i++) {
const code = s.charCodeAt(i);
// If the character is between 'A' (65) and 'Z' (90), convert using bitwise OR with 32
out[i] = code >= 65 && code <= 90 ? String.fromCharCode(code | 32) : s[i];
}
return out.join("");
}
module.exports = {
asciiLowercase
};
+55
View File
@@ -0,0 +1,55 @@
# Blue Oak Model License
Version 1.0.0
## Purpose
This license gives everyone as much permission to work with
this software as possible, while protecting contributors
from liability.
## Acceptance
In order to receive this license, you must agree to its
rules. The rules of this license are both obligations
under that agreement and conditions to your license.
You must not do anything with this software that triggers
a rule that you cannot or will not follow.
## Copyright
Each contributor licenses you to do everything with this
software that would otherwise infringe that contributor's
copyright in it.
## Notices
You must ensure that everyone who gets a copy of
any part of this software from you, with or without
changes, also gets the text of this license or a link to
<https://blueoakcouncil.org/license/1.0.0>.
## Excuse
If anyone notifies you in writing that you have not
complied with [Notices](#notices), you can keep your
license by taking all practical steps to comply within 30
days after the notice. If you do not do so, your license
ends immediately.
## Patent
Each contributor licenses you to do everything with this
software that would otherwise infringe any patent claims
they can license or become able to license.
## Reliability
No contributor can revoke this license.
## No Liability
***As far as the law allows, this software comes as is,
without any warranty or condition, and no contributor
will be liable to anyone for any damages related to this
software or this license, under any kind of legal claim.***
+383
View File
@@ -0,0 +1,383 @@
# lru-cache
A cache object that deletes the least-recently-used items.
Specify a max number of the most recently used items that you
want to keep, and this cache will keep that many of the most
recently accessed items.
This is not primarily a TTL cache, and does not make strong TTL
guarantees. There is no preemptive pruning of expired items by
default, but you _may_ set a TTL on the cache or on a single
`set`. If you do so, it will treat expired items as missing, and
delete them when fetched. If you are more interested in TTL
caching than LRU caching, check out
[@isaacs/ttlcache](http://npm.im/@isaacs/ttlcache).
As of version 7, this is one of the most performant LRU
implementations available in JavaScript, and supports a wide
diversity of use cases. However, note that using some of the
features will necessarily impact performance, by causing the
cache to have to do more work. See the "Performance" section
below.
## Installation
```bash
npm install lru-cache --save
```
## Usage
```js
// hybrid module, either works
import { LRUCache } from 'lru-cache'
// or:
const { LRUCache } = require('lru-cache')
// or in minified form for web browsers:
import { LRUCache } from 'http://unpkg.com/lru-cache@9/dist/mjs/index.min.mjs'
// At least one of 'max', 'ttl', or 'maxSize' is required, to prevent
// unsafe unbounded storage.
//
// In most cases, it's best to specify a max for performance, so all
// the required memory allocation is done up-front.
//
// All the other options are optional, see the sections below for
// documentation on what each one does. Most of them can be
// overridden for specific items in get()/set()
const options = {
max: 500,
// for use with tracking overall storage size
maxSize: 5000,
sizeCalculation: (value, key) => {
return 1
},
// for use when you need to clean up something when objects
// are evicted from the cache
dispose: (value, key, reason) => {
freeFromMemoryOrWhatever(value)
},
// for use when you need to know that an item is being inserted
// note that this does NOT allow you to prevent the insertion,
// it just allows you to know about it.
onInsert: (value, key, reason) => {
logInsertionOrWhatever(key, value)
},
// how long to live in ms
ttl: 1000 * 60 * 5,
// return stale items before removing from cache?
allowStale: false,
updateAgeOnGet: false,
updateAgeOnHas: false,
// async method to use for cache.fetch(), for
// stale-while-revalidate type of behavior
fetchMethod: async (key, staleValue, { options, signal, context }) => {},
}
const cache = new LRUCache(options)
cache.set('key', 'value')
cache.get('key') // "value"
// non-string keys ARE fully supported
// but note that it must be THE SAME object, not
// just a JSON-equivalent object.
var someObject = { a: 1 }
cache.set(someObject, 'a value')
// Object keys are not toString()-ed
cache.set('[object Object]', 'a different value')
assert.equal(cache.get(someObject), 'a value')
// A similar object with same keys/values won't work,
// because it's a different object identity
assert.equal(cache.get({ a: 1 }), undefined)
cache.clear() // empty the cache
```
If you put more stuff in the cache, then less recently used items
will fall out. That's what an LRU cache is.
For full description of the API and all options, please see [the
LRUCache typedocs](https://isaacs.github.io/node-lru-cache/)
## Storage Bounds Safety
This implementation aims to be as flexible as possible, within
the limits of safe memory consumption and optimal performance.
At initial object creation, storage is allocated for `max` items.
If `max` is set to zero, then some performance is lost, and item
count is unbounded. Either `maxSize` or `ttl` _must_ be set if
`max` is not specified.
If `maxSize` is set, then this creates a safe limit on the
maximum storage consumed, but without the performance benefits of
pre-allocation. When `maxSize` is set, every item _must_ provide
a size, either via the `sizeCalculation` method provided to the
constructor, or via a `size` or `sizeCalculation` option provided
to `cache.set()`. The size of every item _must_ be a positive
integer.
If neither `max` nor `maxSize` are set, then `ttl` tracking must
be enabled. Note that, even when tracking item `ttl`, items are
_not_ preemptively deleted when they become stale, unless
`ttlAutopurge` is enabled. Instead, they are only purged the
next time the key is requested. Thus, if `ttlAutopurge`, `max`,
and `maxSize` are all not set, then the cache will potentially
grow unbounded.
In this case, a warning is printed to standard error. Future
versions may require the use of `ttlAutopurge` if `max` and
`maxSize` are not specified.
If you truly wish to use a cache that is bound _only_ by TTL
expiration, consider using a `Map` object, and calling
`setTimeout` to delete entries when they expire. It will perform
much better than an LRU cache.
Here is an implementation you may use, under the same
[license](./LICENSE) as this package:
```js
// a storage-unbounded ttl cache that is not an lru-cache
const cache = {
data: new Map(),
timers: new Map(),
set: (k, v, ttl) => {
if (cache.timers.has(k)) {
clearTimeout(cache.timers.get(k))
}
cache.timers.set(
k,
setTimeout(() => cache.delete(k), ttl),
)
cache.data.set(k, v)
},
get: k => cache.data.get(k),
has: k => cache.data.has(k),
delete: k => {
if (cache.timers.has(k)) {
clearTimeout(cache.timers.get(k))
}
cache.timers.delete(k)
return cache.data.delete(k)
},
clear: () => {
cache.data.clear()
for (const v of cache.timers.values()) {
clearTimeout(v)
}
cache.timers.clear()
},
}
```
If that isn't to your liking, check out
[@isaacs/ttlcache](http://npm.im/@isaacs/ttlcache).
## Storing Undefined Values
This cache never stores undefined values, as `undefined` is used
internally in a few places to indicate that a key is not in the
cache.
You may call `cache.set(key, undefined)`, but this is just
an alias for `cache.delete(key)`. Note that this has the effect
that `cache.has(key)` will return _false_ after setting it to
undefined.
```js
cache.set(myKey, undefined)
cache.has(myKey) // false!
```
If you need to track `undefined` values, and still note that the
key is in the cache, an easy workaround is to use a sigil object
of your own.
```js
import { LRUCache } from 'lru-cache'
const undefinedValue = Symbol('undefined')
const cache = new LRUCache(...)
const mySet = (key, value) =>
cache.set(key, value === undefined ? undefinedValue : value)
const myGet = (key, value) => {
const v = cache.get(key)
return v === undefinedValue ? undefined : v
}
```
## Performance
As of January 2022, version 7 of this library is one of the most
performant LRU cache implementations in JavaScript.
Benchmarks can be extremely difficult to get right. In
particular, the performance of set/get/delete operations on
objects will vary _wildly_ depending on the type of key used. V8
is highly optimized for objects with keys that are short strings,
especially integer numeric strings. Thus any benchmark which
tests _solely_ using numbers as keys will tend to find that an
object-based approach performs the best.
Note that coercing _anything_ to strings to use as object keys is
unsafe, unless you can be 100% certain that no other type of
value will be used. For example:
```js
const myCache = {}
const set = (k, v) => (myCache[k] = v)
const get = k => myCache[k]
set({}, 'please hang onto this for me')
set('[object Object]', 'oopsie')
```
Also beware of "Just So" stories regarding performance. Garbage
collection of large (especially: deep) object graphs can be
incredibly costly, with several "tipping points" where it
increases exponentially. As a result, putting that off until
later can make it much worse, and less predictable. If a library
performs well, but only in a scenario where the object graph is
kept shallow, then that won't help you if you are using large
objects as keys.
In general, when attempting to use a library to improve
performance (such as a cache like this one), it's best to choose
an option that will perform well in the sorts of scenarios where
you'll actually use it.
This library is optimized for repeated gets and minimizing
eviction time, since that is the expected need of a LRU. Set
operations are somewhat slower on average than a few other
options, in part because of that optimization. It is assumed
that you'll be caching some costly operation, ideally as rarely
as possible, so optimizing set over get would be unwise.
If performance matters to you:
1. If it's at all possible to use small integer values as keys,
and you can guarantee that no other types of values will be
used as keys, then do that, and use a cache such as
[lru-fast](https://npmjs.com/package/lru-fast), or
[mnemonist's
LRUCache](https://yomguithereal.github.io/mnemonist/lru-cache)
which uses an Object as its data store.
2. Failing that, if at all possible, use short non-numeric
strings (ie, less than 256 characters) as your keys, and use
[mnemonist's
LRUCache](https://yomguithereal.github.io/mnemonist/lru-cache).
3. If the types of your keys will be anything else, especially
long strings, strings that look like floats, objects, or some
mix of types, or if you aren't sure, then this library will
work well for you.
If you do not need the features that this library provides
(like asynchronous fetching, a variety of TTL staleness
options, and so on), then [mnemonist's
LRUMap](https://yomguithereal.github.io/mnemonist/lru-map) is
a very good option, and just slightly faster than this module
(since it does considerably less).
4. Do not use a `dispose` function, size tracking, or especially
ttl behavior, unless absolutely needed. These features are
convenient, and necessary in some use cases, and every attempt
has been made to make the performance impact minimal, but it
isn't nothing.
## Testing
When writing tests that involve TTL-related functionality, note
that this module creates an internal reference to the global
`performance` or `Date` objects at import time. If you import it
statically at the top level, those references cannot be mocked or
overridden in your test environment.
To avoid this, dynamically import the package within your tests
so that the references are captured after your mocks are applied.
For example:
```ts
// ❌ Not recommended
import { LRUCache } from 'lru-cache'
// mocking timers, e.g. jest.useFakeTimers()
// ✅ Recommended for TTL tests
// mocking timers, e.g. jest.useFakeTimers()
const { LRUCache } = await import('lru-cache')
```
This ensures that your mocked timers or time sources are
respected when testing TTL behavior.
Additionally, you can pass in a `perf` option when creating your
LRUCache instance. This option accepts any object with a `now`
method that returns a number.
For example, this would be a very bare-bones time-mocking system
you could use in your tests, without any particular test
framework:
```ts
import { LRUCache } from 'lru-cache'
let myClockTime = 0
const cache = new LRUCache<string>({
max: 10,
ttl: 1000,
perf: {
now: () => myClockTime,
},
})
// run tests, updating myClockTime as needed
```
## Breaking Changes in Version 7
This library changed to a different algorithm and internal data
structure in version 7, yielding significantly better
performance, albeit with some subtle changes as a result.
If you were relying on the internals of LRUCache in version 6 or
before, it probably will not work in version 7 and above.
## Breaking Changes in Version 8
- The `fetchContext` option was renamed to `context`, and may no
longer be set on the cache instance itself.
- Rewritten in TypeScript, so pretty much all the types moved
around a lot.
- The AbortController/AbortSignal polyfill was removed. For this
reason, **Node version 16.14.0 or higher is now required**.
- Internal properties were moved to actual private class
properties.
- Keys and values must not be `null` or `undefined`.
- Minified export available at `'lru-cache/min'`, for both CJS
and MJS builds.
## Breaking Changes in Version 9
- Named export only, no default export.
- AbortController polyfill returned, albeit with a warning when
used.
## Breaking Changes in Version 10
- `cache.fetch()` return type is now `Promise<V | undefined>`
instead of `Promise<V | void>`. This is an irrelevant change
practically speaking, but can require changes for TypeScript
users.
For more info, see the [change log](CHANGELOG.md).
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
{
"type": "commonjs"
}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More