diff options
Diffstat (limited to 'vanilla/node_modules/cssstyle/lib/CSSStyleDeclaration.js')
| -rw-r--r-- | vanilla/node_modules/cssstyle/lib/CSSStyleDeclaration.js | 649 |
1 files changed, 649 insertions, 0 deletions
diff --git a/vanilla/node_modules/cssstyle/lib/CSSStyleDeclaration.js b/vanilla/node_modules/cssstyle/lib/CSSStyleDeclaration.js new file mode 100644 index 0000000..ab1dcb1 --- /dev/null +++ b/vanilla/node_modules/cssstyle/lib/CSSStyleDeclaration.js @@ -0,0 +1,649 @@ +/** + * This is a fork from the CSS Style Declaration part of + * https://github.com/NV/CSSOM + */ +"use strict"; + +const allProperties = require("./generated/allProperties"); +const implementedProperties = require("./generated/implementedProperties"); +const generatedProperties = require("./generated/properties"); +const { + borderProperties, + getPositionValue, + normalizeProperties, + prepareBorderProperties, + prepareProperties, + shorthandProperties +} = require("./normalize"); +const { + hasVarFunc, + isGlobalKeyword, + parseCSS, + parsePropertyValue, + prepareValue +} = require("./parsers"); +const allExtraProperties = require("./utils/allExtraProperties"); +const { dashedToCamelCase } = require("./utils/camelize"); +const { getPropertyDescriptor } = require("./utils/propertyDescriptors"); +const { asciiLowercase } = require("./utils/strings"); + +/** + * @see https://drafts.csswg.org/cssom/#the-cssstyledeclaration-interface + */ +class CSSStyleDeclaration { + /** + * @param {Function} onChangeCallback + * @param {object} [opt] + * @param {object} [opt.context] - Window, Element or CSSRule. + */ + constructor(onChangeCallback, opt = {}) { + // Make constructor and internals non-enumerable. + Object.defineProperties(this, { + constructor: { + enumerable: false, + writable: true + }, + + // Window + _global: { + value: globalThis, + enumerable: false, + writable: true + }, + + // Element + _ownerNode: { + value: null, + enumerable: false, + writable: true + }, + + // CSSRule + _parentNode: { + value: null, + enumerable: false, + writable: true + }, + + _onChange: { + value: null, + enumerable: false, + writable: true + }, + + _values: { + value: new Map(), + enumerable: false, + writable: true + }, + + _priorities: { + value: new Map(), + enumerable: false, + writable: true + }, + + _length: { + value: 0, + enumerable: false, + writable: true + }, + + _computed: { + value: false, + enumerable: false, + writable: true + }, + + _readonly: { + value: false, + enumerable: false, + writable: true + }, + + _setInProgress: { + value: false, + enumerable: false, + writable: true + } + }); + + const { context } = opt; + if (context) { + if (typeof context.getComputedStyle === "function") { + this._global = context; + this._computed = 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; + } + } + } + if (typeof onChangeCallback === "function") { + this._onChange = onChangeCallback; + } + } + + 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(" "); + } + + set cssText(val) { + if (this._readonly) { + const msg = "cssText can not be modified."; + const name = "NoModificationAllowedError"; + throw new this._global.DOMException(msg, name); + } + Array.prototype.splice.call(this, 0, this._length); + this._values.clear(); + this._priorities.clear(); + if (this._parentRule || (this._ownerNode && this._setInProgress)) { + return; + } + try { + this._setInProgress = true; + const valueObj = parseCSS( + val, + { + context: "declarationList", + parseValue: false + }, + true + ); + 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, { + globalObject: this._global + }); + 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, { + globalObject: this._global + }); + for (const [property, item] of parsedProperties) { + const { priority, value } = item; + this._priorities.set(property, priority); + this.setProperty(property, value, priority); + } + } + } catch { + return; + } finally { + this._setInProgress = false; + } + if (typeof this._onChange === "function") { + this._onChange(this.cssText); + } + } + + get length() { + return this._length; + } + + // This deletes indices if the new length is less then the current length. + // If the new length is more, it does nothing, the new indices will be + // undefined until set. + set length(len) { + for (let i = len; i < this._length; i++) { + delete this[i]; + } + this._length = len; + } + + // Readonly + get parentRule() { + return this._parentRule; + } + + get cssFloat() { + return this.getPropertyValue("float"); + } + + set cssFloat(value) { + this._setProperty("float", value); + } + + /** + * @param {string} property + */ + getPropertyPriority(property) { + return this._priorities.get(property) || ""; + } + + /** + * @param {string} property + */ + getPropertyValue(property) { + if (this._values.has(property)) { + return this._values.get(property).toString(); + } + return ""; + } + + /** + * @param {...number} args + */ + 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]; + } + + /** + * @param {string} 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 = Array.prototype.indexOf.call(this, property); + if (index >= 0) { + Array.prototype.splice.call(this, index, 1); + if (typeof this._onChange === "function") { + this._onChange(this.cssText); + } + } + return prevValue; + } + + /** + * @param {string} prop + * @param {string} val + * @param {string} prior + */ + setProperty(prop, val, prior) { + if (this._readonly) { + const msg = `Property ${prop} can not be modified.`; + const name = "NoModificationAllowedError"; + throw new this._global.DOMException(msg, name); + } + const value = prepareValue(val); + if (value === "") { + this[prop] = ""; + this.removeProperty(prop); + return; + } + const priority = prior === "important" ? "important" : ""; + const isCustomProperty = prop.startsWith("--"); + if (isCustomProperty) { + this._setProperty(prop, value, priority); + return; + } + const property = asciiLowercase(prop); + if (!allProperties.has(property) && !allExtraProperties.has(property)) { + return; + } + if (priority) { + this._priorities.set(property, priority); + } else { + this._priorities.delete(property); + } + this[property] = value; + } +} + +// Internal methods +Object.defineProperties(CSSStyleDeclaration.prototype, { + _setProperty: { + /** + * @param {string} property + * @param {string} val + * @param {string} priority + */ + value(property, val, priority) { + if (typeof val !== "string") { + return; + } + if (val === "") { + this.removeProperty(property); + return; + } + let originalText = ""; + if (typeof this._onChange === "function" && !this._setInProgress) { + originalText = this.cssText; + } + if (this._values.has(property)) { + const index = Array.prototype.indexOf.call(this, property); + // The property already exists but is not indexed into `this` so add it. + if (index < 0) { + this[this._length] = property; + this._length++; + } + } else { + // New property. + this[this._length] = property; + this._length++; + } + if (priority === "important") { + this._priorities.set(property, priority); + } else { + this._priorities.delete(property); + } + this._values.set(property, val); + if ( + typeof this._onChange === "function" && + !this._setInProgress && + this.cssText !== originalText + ) { + this._onChange(this.cssText); + } + }, + enumerable: false + }, + + _borderSetter: { + /** + * @param {string} prop + * @param {object|Array|string} val + * @param {string} prior + */ + value(prop, val, prior) { + const properties = new Map(); + if (prop === "border") { + let priority = ""; + if (typeof prior === "string") { + priority = prior; + } else { + priority = this._priorities.get(prop) ?? ""; + } + properties.set(prop, { propery: prop, value: val, priority }); + } else { + for (let i = 0; i < this._length; i++) { + const property = this[i]; + if (borderProperties.has(property)) { + const value = this.getPropertyValue(property); + const longhandPriority = this._priorities.get(property) ?? ""; + let priority = longhandPriority; + if (prop === property && typeof prior === "string") { + priority = prior; + } + properties.set(property, { property, value, priority }); + } + } + } + const parsedProperties = prepareBorderProperties(prop, val, prior, properties, { + globalObject: this._global + }); + for (const [property, item] of parsedProperties) { + const { priority, value } = item; + this._setProperty(property, value, priority); + } + }, + enumerable: false + }, + + _flexBoxSetter: { + /** + * @param {string} prop + * @param {string} val + * @param {string} prior + * @param {string} shorthandProperty + */ + value(prop, val, prior, shorthandProperty) { + if (!shorthandProperty || !shorthandProperties.has(shorthandProperty)) { + return; + } + const shorthandPriority = this._priorities.get(shorthandProperty); + this.removeProperty(shorthandProperty); + let priority = ""; + if (typeof prior === "string") { + priority = prior; + } else { + priority = this._priorities.get(prop) ?? ""; + } + this.removeProperty(prop); + if (shorthandPriority && priority) { + this._setProperty(prop, val); + } else { + this._setProperty(prop, val, priority); + } + if (val && !hasVarFunc(val)) { + const longhandValues = []; + const shorthandItem = shorthandProperties.get(shorthandProperty); + let hasGlobalKeyword = false; + for (const [longhandProperty] of shorthandItem.shorthandFor) { + if (longhandProperty === prop) { + if (isGlobalKeyword(val)) { + hasGlobalKeyword = true; + } + longhandValues.push(val); + } 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((value) => value === firstValue)) { + this._setProperty(shorthandProperty, firstValue, priority); + } + } else { + const parsedValue = shorthandItem.parse(longhandValues.join(" ")); + const shorthandValue = Object.values(parsedValue).join(" "); + this._setProperty(shorthandProperty, shorthandValue, priority); + } + } + } + }, + enumerable: false + }, + + _positionShorthandSetter: { + /** + * @param {string} prop + * @param {Array|string} val + * @param {string} prior + */ + value(prop, val, prior) { + if (!shorthandProperties.has(prop)) { + return; + } + const shorthandValues = []; + if (Array.isArray(val)) { + shorthandValues.push(...val); + } else if (typeof val === "string") { + shorthandValues.push(val); + } else { + return; + } + let priority = ""; + if (typeof prior === "string") { + priority = prior; + } else { + priority = this._priorities.get(prop) ?? ""; + } + const { position, shorthandFor } = shorthandProperties.get(prop); + 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(prop); + } else { + const shorthandValue = getPositionValue(shorthandValues, position); + this._setProperty(prop, shorthandValue, priority); + } + }, + enumerable: false + }, + + _positionLonghandSetter: { + /** + * @param {string} prop + * @param {string} val + * @param {string} prior + * @param {string} shorthandProperty + */ + value(prop, val, prior, shorthandProperty) { + if (!shorthandProperty || !shorthandProperties.has(shorthandProperty)) { + return; + } + const shorthandPriority = this._priorities.get(shorthandProperty); + this.removeProperty(shorthandProperty); + let priority = ""; + if (typeof prior === "string") { + priority = prior; + } else { + priority = this._priorities.get(prop) ?? ""; + } + this.removeProperty(prop); + if (shorthandPriority && priority) { + this._setProperty(prop, val); + } else { + this._setProperty(prop, val, priority); + } + if (val && !hasVarFunc(val)) { + const longhandValues = []; + const { shorthandFor, position: shorthandPosition } = + shorthandProperties.get(shorthandProperty); + for (const [longhandProperty] of shorthandFor) { + const longhandValue = this.getPropertyValue(longhandProperty); + const longhandPriority = this._priorities.get(longhandProperty) ?? ""; + if (!longhandValue || longhandPriority !== priority) { + return; + } + longhandValues.push(longhandValue); + } + if (longhandValues.length === shorthandFor.size) { + const replacedValue = getPositionValue(longhandValues, shorthandPosition); + this._setProperty(shorthandProperty, replacedValue); + } + } + }, + enumerable: false + } +}); + +// Properties +Object.defineProperties(CSSStyleDeclaration.prototype, generatedProperties); + +// Additional properties +[...allProperties, ...allExtraProperties].forEach((property) => { + if (!implementedProperties.has(property)) { + const declaration = getPropertyDescriptor(property); + Object.defineProperty(CSSStyleDeclaration.prototype, property, declaration); + const camel = dashedToCamelCase(property); + Object.defineProperty(CSSStyleDeclaration.prototype, camel, declaration); + if (/^webkit[A-Z]/.test(camel)) { + const pascal = camel.replace(/^webkit/, "Webkit"); + Object.defineProperty(CSSStyleDeclaration.prototype, pascal, declaration); + } + } +}); + +module.exports = { + CSSStyleDeclaration, + propertyList: Object.fromEntries(implementedProperties) +}; |
