diff --git a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts index 264b4aefb..155af69ba 100644 --- a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts +++ b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts @@ -3,10 +3,8 @@ import Attr from '../../nodes/attr/Attr'; import CSSRule from '../CSSRule'; import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum'; import DOMException from '../../exception/DOMException'; -import CSSStyleDeclarationStyleString from './utilities/CSSStyleDeclarationStyleString'; +import CSSStyleDeclarationElement from './utilities/CSSStyleDeclarationElement'; import CSSStyleDeclarationPropertyManager from './utilities/CSSStyleDeclarationPropertyManager'; -import ICSSStyleDeclarationProperty from './utilities/ICSSStyleDeclarationPropertyValue'; -import CSSStyleDeclarationPropertyReader from './utilities/CSSStyleDeclarationPropertyReader'; /** * CSS Style Declaration. @@ -14,7 +12,7 @@ import CSSStyleDeclarationPropertyReader from './utilities/CSSStyleDeclarationPr export default abstract class AbstractCSSStyleDeclaration { // Other properties public readonly parentRule: CSSRule = null; - protected _styles: { [k: string]: ICSSStyleDeclarationProperty } = {}; + protected _style: CSSStyleDeclarationPropertyManager = null; protected _ownerElement: IElement; protected _computed: boolean; @@ -25,8 +23,9 @@ export default abstract class AbstractCSSStyleDeclaration { * @param [computed] Computed. */ constructor(ownerElement: IElement = null, computed = false) { + this._style = !ownerElement ? new CSSStyleDeclarationPropertyManager() : null; this._ownerElement = ownerElement; - this._computed = computed; + this._computed = ownerElement ? computed : false; } /** @@ -36,12 +35,13 @@ export default abstract class AbstractCSSStyleDeclaration { */ public get length(): number { if (this._ownerElement) { - return Object.keys( - CSSStyleDeclarationStyleString.getElementStyleProperties(this._ownerElement, this._computed) - ).length; + const style = new CSSStyleDeclarationPropertyManager( + CSSStyleDeclarationElement.getElementStyle(this._ownerElement, this._computed) + ); + return style.size(); } - return Object.keys(this._styles).length; + return this._style.size(); } /** @@ -55,12 +55,13 @@ export default abstract class AbstractCSSStyleDeclaration { return ''; } - return CSSStyleDeclarationStyleString.getStyleString( - CSSStyleDeclarationStyleString.getElementStyleProperties(this._ownerElement, this._computed) + const style = new CSSStyleDeclarationPropertyManager( + CSSStyleDeclarationElement.getElementStyle(this._ownerElement, false) ); + return style.toString(); } - return CSSStyleDeclarationStyleString.getStyleString(this._styles); + return this._style.toString(); } /** @@ -77,10 +78,8 @@ export default abstract class AbstractCSSStyleDeclaration { } if (this._ownerElement) { - const parsed = CSSStyleDeclarationStyleString.getStyleString( - CSSStyleDeclarationStyleString.getStyleProperties(cssText) - ); - if (!parsed) { + const style = new CSSStyleDeclarationPropertyManager(cssText); + if (!style.size()) { delete this._ownerElement['_attributes']['style']; } else { if (!this._ownerElement['_attributes']['style']) { @@ -89,10 +88,10 @@ export default abstract class AbstractCSSStyleDeclaration { this._ownerElement['_attributes']['style'].name = 'style'; } - this._ownerElement['_attributes']['style'].value = parsed; + this._ownerElement['_attributes']['style'].value = style.toString(); } } else { - this._styles = CSSStyleDeclarationStyleString.getStyleProperties(cssText); + this._style = new CSSStyleDeclarationPropertyManager(cssText); } } @@ -104,33 +103,25 @@ export default abstract class AbstractCSSStyleDeclaration { */ public item(index: number): string { if (this._ownerElement) { - return ( - Object.keys( - CSSStyleDeclarationStyleString.getElementStyleProperties( - this._ownerElement, - this._computed - ) - )[index] || '' + const style = new CSSStyleDeclarationPropertyManager( + CSSStyleDeclarationElement.getElementStyle(this._ownerElement, this._computed) ); + return style.item(index); } - return Object.keys(this._styles)[index] || ''; + return this._style.item(index); } /** * Set a property. * - * @param propertyName Property name. + * @param name Property name. * @param value Value. Must not contain "!important" as that should be set using the priority parameter. * @param [priority] Can be "important", or an empty string. */ - public setProperty( - propertyName: string, - value: string, - priority?: 'important' | '' | undefined - ): void { + public setProperty(name: string, value: string, priority?: 'important' | '' | undefined): void { if (this._computed) { throw new DOMException( - `Failed to execute 'setProperty' on 'CSSStyleDeclaration': These styles are computed, and therefore the '${propertyName}' property is read-only.`, + `Failed to execute 'setProperty' on 'CSSStyleDeclaration': These styles are computed, and therefore the '${name}' property is read-only.`, DOMExceptionNameEnum.domException ); } @@ -140,7 +131,7 @@ export default abstract class AbstractCSSStyleDeclaration { } if (!value) { - this.removeProperty(propertyName); + this.removeProperty(name); } else if (this._ownerElement) { if (!this._ownerElement['_attributes']['style']) { Attr._ownerDocument = this._ownerElement.ownerDocument; @@ -148,97 +139,61 @@ export default abstract class AbstractCSSStyleDeclaration { this._ownerElement['_attributes']['style'].name = 'style'; } - const elementStyleProperties = CSSStyleDeclarationStyleString.getElementStyleProperties( - this._ownerElement, - this._computed - ); - - Object.assign( - elementStyleProperties, - CSSStyleDeclarationPropertyManager.getRelatedProperties({ - name: propertyName, - value, - important: !!priority - }) + const style = new CSSStyleDeclarationPropertyManager( + CSSStyleDeclarationElement.getElementStyle(this._ownerElement, false) ); + style.set(name, value, !!priority); - this._ownerElement['_attributes']['style'].value = - CSSStyleDeclarationStyleString.getStyleString(elementStyleProperties); + this._ownerElement['_attributes']['style'].value = style.toString(); } else { - Object.assign( - this._styles, - CSSStyleDeclarationPropertyManager.getRelatedProperties({ - name: propertyName, - value, - important: !!priority - }) - ); + this._style.set(name, value, !!priority); } } /** * Removes a property. * - * @param propertyName Property name in kebab case. + * @param name Property name in kebab case. * @param value Value. Must not contain "!important" as that should be set using the priority parameter. * @param [priority] Can be "important", or an empty string. */ - public removeProperty(propertyName: string): void { + public removeProperty(name: string): void { if (this._computed) { throw new DOMException( - `Failed to execute 'removeProperty' on 'CSSStyleDeclaration': These styles are computed, and therefore the '${propertyName}' property is read-only.`, + `Failed to execute 'removeProperty' on 'CSSStyleDeclaration': These styles are computed, and therefore the '${name}' property is read-only.`, DOMExceptionNameEnum.domException ); } if (this._ownerElement) { - if (this._ownerElement['_attributes']['style']) { - const elementStyleProperties = CSSStyleDeclarationStyleString.getElementStyleProperties( - this._ownerElement, - this._computed - ); - const propertiesToRemove = - CSSStyleDeclarationPropertyManager.getRelatedPropertyNames(propertyName); - - for (const property of Object.keys(propertiesToRemove)) { - delete elementStyleProperties[property]; - } - - const styleString = CSSStyleDeclarationStyleString.getStyleString(elementStyleProperties); - - if (styleString) { - this._ownerElement['_attributes']['style'].value = styleString; - } else { - delete this._ownerElement['_attributes']['style']; - } + const style = new CSSStyleDeclarationPropertyManager( + CSSStyleDeclarationElement.getElementStyle(this._ownerElement, false) + ); + style.remove(name); + const newCSSText = style.toString(); + if (newCSSText) { + this._ownerElement['_attributes']['style'].value = newCSSText; + } else { + delete this._ownerElement['_attributes']['style']; } } else { - const propertiesToRemove = - CSSStyleDeclarationPropertyManager.getRelatedPropertyNames(propertyName); - - for (const property of Object.keys(propertiesToRemove)) { - delete this._styles[property]; - } + this._style.remove(name); } } /** * Returns a property. * - * @param propertyName Property name in kebab case. + * @param name Property name in kebab case. * @returns Property value. */ - public getPropertyValue(propertyName: string): string { + public getPropertyValue(name: string): string { if (this._ownerElement) { - const elementStyleProperties = CSSStyleDeclarationStyleString.getElementStyleProperties( - this._ownerElement, - this._computed - ); - return CSSStyleDeclarationPropertyReader.getPropertyValue( - elementStyleProperties, - propertyName + const style = new CSSStyleDeclarationPropertyManager( + CSSStyleDeclarationElement.getElementStyle(this._ownerElement, false) ); + return style.get(name)?.value || ''; } - return CSSStyleDeclarationPropertyReader.getPropertyValue(this._styles, propertyName); + return this._style.get(name)?.value || ''; } } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts new file mode 100644 index 000000000..0b93386a3 --- /dev/null +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts @@ -0,0 +1,24 @@ +import IElement from '../../../nodes/element/IElement'; + +/** + * CSS Style Declaration utility + */ +export default class CSSStyleDeclarationElement { + /** + * Returns element style properties. + * + * @param element Element. + * @param [computed] Computed. + * @returns Element style properties. + */ + public static getElementStyle(element: IElement, computed: boolean): string { + if (computed) { + // TODO: Add logic for style sheets + } + if (element['_attributes']['style'] && element['_attributes']['style'].value) { + return element['_attributes']['style'].value; + } + + return null; + } +} diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts new file mode 100644 index 000000000..8d314eb7b --- /dev/null +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts @@ -0,0 +1,437 @@ +import ICSSStyleDeclarationPropertyValue from './ICSSStyleDeclarationPropertyValue'; + +/** + * Computed style property parser. + */ +export default class CSSStyleDeclarationPropertyGetParser { + /** + * Returns margin. + * + * @param properties Properties. + * @returns Property value + */ + public static getMargin(properties: { + [k: string]: ICSSStyleDeclarationPropertyValue; + }): ICSSStyleDeclarationPropertyValue { + if (!properties['margin-top']?.value) { + return null; + } + return { + important: ![ + properties['margin-top']?.important, + properties['margin-bottom']?.important, + properties['margin-left']?.important, + properties['margin-right']?.important + ].some((important) => important === false), + value: `${properties['margin-top'].value} ${properties['margin-right']?.value || ''} ${ + properties['margin-top'].value !== properties['margin-bottom']?.value + ? properties['margin-bottom']?.value || '' + : '' + } ${ + properties['margin-right'].value !== properties['margin-left']?.value + ? properties['margin-left']?.value || '' + : '' + }` + .replace(/ /g, '') + .trim() + }; + } + + /** + * Returns padding. + * + * @param properties Properties. + * @returns Property value + */ + public static getPadding(properties: { + [k: string]: ICSSStyleDeclarationPropertyValue; + }): ICSSStyleDeclarationPropertyValue { + if (!properties['padding-top']?.value) { + return null; + } + return { + important: ![ + properties['padding-top']?.important, + properties['padding-bottom']?.important, + properties['padding-left']?.important, + properties['padding-right']?.important + ].some((important) => important === false), + value: `${properties['padding-top'].value} ${properties['padding-right']?.value || ''} ${ + properties['padding-top'].value !== properties['padding-bottom']?.value + ? properties['padding-bottom']?.value || '' + : '' + } ${ + properties['padding-right'].value !== properties['padding-left']?.value + ? properties['padding-left']?.value || '' + : '' + }` + .replace(/ /g, '') + .trim() + }; + } + + /** + * Returns border. + * + * @param properties Properties. + * @returns Property value + */ + public static getBorder(properties: { + [k: string]: ICSSStyleDeclarationPropertyValue; + }): ICSSStyleDeclarationPropertyValue { + if ( + !properties['border-top-width']?.value || + properties['border-right-width']?.value !== properties['border-top-width']?.value || + properties['border-right-style']?.value !== properties['border-top-style']?.value || + properties['border-right-color']?.value !== properties['border-top-color']?.value || + properties['border-bottom-width']?.value !== properties['border-top-width']?.value || + properties['border-bottom-style']?.value !== properties['border-top-style']?.value || + properties['border-bottom-color']?.value !== properties['border-top-color']?.value || + properties['border-left-width']?.value !== properties['border-top-width']?.value || + properties['border-left-style']?.value !== properties['border-top-style']?.value || + properties['border-left-color']?.value !== properties['border-top-color']?.value + ) { + return null; + } + return { + important: ![ + properties['border-top-width']?.important, + properties['border-right-width']?.important, + properties['border-bottom-width']?.important, + properties['border-left-width']?.important, + properties['border-top-style']?.important, + properties['border-right-style']?.important, + properties['border-bottom-style']?.important, + properties['border-left-style']?.important, + properties['border-top-color']?.important, + properties['border-right-color']?.important, + properties['border-bottom-color']?.important, + properties['border-left-color']?.important + ].some((important) => important === false), + value: `${properties['border-top-width'].value} ${ + properties['border-top-style']?.value || '' + } ${properties['border-top-color']?.value || ''}` + .replace(/ /g, '') + .trim() + }; + } + + /** + * Returns border. + * + * @param properties Properties. + * @returns Property value + */ + public static getBorderTop(properties: { + [k: string]: ICSSStyleDeclarationPropertyValue; + }): ICSSStyleDeclarationPropertyValue { + if (!properties['border-top-width']?.value) { + return null; + } + return { + important: ![ + properties['border-top-width']?.important, + properties['border-top-style']?.important, + properties['border-top-color']?.important + ].some((important) => important === false), + value: `${properties['border-top-width'].value} ${ + properties['border-top-style']?.value || '' + } ${properties['border-top-color']?.value || ''}` + .replace(/ /g, '') + .trim() + }; + } + + /** + * Returns border. + * + * @param properties Properties. + * @returns Property value + */ + public static getBorderRight(properties: { + [k: string]: ICSSStyleDeclarationPropertyValue; + }): ICSSStyleDeclarationPropertyValue { + if (!properties['border-right-width']?.value) { + return null; + } + return { + important: ![ + properties['border-right-width']?.important, + properties['border-right-style']?.important, + properties['border-right-color']?.important + ].some((important) => important === false), + value: `${properties['border-right-width'].value} ${ + properties['border-right-style']?.value || '' + } ${properties['border-right-color']?.value || ''}` + .replace(/ /g, '') + .trim() + }; + } + + /** + * Returns border. + * + * @param properties Properties. + * @returns Property value + */ + public static getBorderBottom(properties: { + [k: string]: ICSSStyleDeclarationPropertyValue; + }): ICSSStyleDeclarationPropertyValue { + if (!properties['border-bottom-width']?.value) { + return null; + } + return { + important: ![ + properties['border-bottom-width']?.important, + properties['border-bottom-style']?.important, + properties['border-bottom-color']?.important + ].some((important) => important === false), + value: `${properties['border-bottom-width'].value} ${ + properties['border-bottom-style']?.value || '' + } ${properties['border-bottom-color']?.value || ''}` + .replace(/ /g, '') + .trim() + }; + } + + /** + * Returns border. + * + * @param properties Properties. + * @returns Property value + */ + public static getBorderLeft(properties: { + [k: string]: ICSSStyleDeclarationPropertyValue; + }): ICSSStyleDeclarationPropertyValue { + if (!properties['border-left-width']?.value) { + return null; + } + return { + important: ![ + properties['border-left-width']?.important, + properties['border-left-style']?.important, + properties['border-left-color']?.important + ].some((important) => important === false), + value: `${properties['border-left-width'].value} ${ + properties['border-left-style']?.value || '' + } ${properties['border-left-color']?.value || ''}` + .replace(/ /g, '') + .trim() + }; + } + + /** + * Returns border. + * + * @param properties Properties. + * @returns Property value + */ + public static getBorderColor(properties: { + [k: string]: ICSSStyleDeclarationPropertyValue; + }): ICSSStyleDeclarationPropertyValue { + if ( + !properties['border-top-color']?.value || + properties['border-top-color']?.value !== properties['border-right-color']?.value || + properties['border-top-color']?.value !== properties['border-bottom-color']?.value || + properties['border-top-color']?.value !== properties['border-left-color']?.value + ) { + return null; + } + return { + important: ![ + properties['border-top-color']?.important, + properties['border-right-color']?.important, + properties['border-bottom-color']?.important, + properties['border-left-color']?.important + ].some((important) => important === false), + value: properties['border-top-color'].value + }; + } + + /** + * Returns border. + * + * @param properties Properties. + * @returns Property value + */ + public static getBorderWidth(properties: { + [k: string]: ICSSStyleDeclarationPropertyValue; + }): ICSSStyleDeclarationPropertyValue { + if ( + !properties['border-top-width']?.value || + properties['border-top-width']?.value !== properties['border-right-width']?.value || + properties['border-top-width']?.value !== properties['border-bottom-width']?.value || + properties['border-top-width']?.value !== properties['border-left-width']?.value + ) { + return null; + } + return { + important: ![ + properties['border-top-width']?.important, + properties['border-right-width']?.important, + properties['border-bottom-width']?.important, + properties['border-left-width']?.important + ].some((important) => important === false), + value: properties['border-top-width'].value + }; + } + + /** + * Returns border. + * + * @param properties Properties. + * @returns Property value + */ + public static getBorderStyle(properties: { + [k: string]: ICSSStyleDeclarationPropertyValue; + }): ICSSStyleDeclarationPropertyValue { + if ( + !properties['border-top-style']?.value || + properties['border-top-style']?.value !== properties['border-right-style']?.value || + properties['border-top-style']?.value !== properties['border-bottom-style']?.value || + properties['border-top-style']?.value !== properties['border-left-style']?.value + ) { + return null; + } + return { + important: ![ + properties['border-top-style']?.important, + properties['border-right-style']?.important, + properties['border-bottom-style']?.important, + properties['border-left-style']?.important + ].some((important) => important === false), + value: properties['border-top-style'].value + }; + } + + /** + * Returns border radius. + * + * @param properties Properties. + * @returns Property value + */ + public static getBorderRadius(properties: { + [k: string]: ICSSStyleDeclarationPropertyValue; + }): ICSSStyleDeclarationPropertyValue { + if (!properties['border-top-left-radius']?.value) { + return null; + } + return { + important: ![ + properties['border-top-left-radius']?.important, + properties['border-top-right-radius']?.important, + properties['border-bottom-right-radius']?.important, + properties['border-bottom-left-radius']?.important + ].some((important) => important === false), + value: `${properties['border-top-left-radius'].value} ${ + properties['border-top-right-radius'].value || '' + } ${ + properties['border-top-left-radius'].value !== + properties['border-bottom-right-radius'].value + ? properties['border-bottom-right-radius'].value || '' + : '' + } ${ + properties['border-top-right-radius'].value !== + properties['border-bottom-left-radius'].value + ? properties['border-bottom-left-radius'].value || '' + : '' + }` + .replace(/ /g, '') + .trim() + }; + } + + /** + * Returns background. + * + * @param properties Properties. + * @returns Property value + */ + public static getBackground(properties: { + [k: string]: ICSSStyleDeclarationPropertyValue; + }): ICSSStyleDeclarationPropertyValue { + if (!properties['background-color']?.value && !properties['background-image']?.value) { + return null; + } + return { + important: ![ + properties['background-color']?.important, + properties['background-image']?.important, + properties['background-repeat']?.important, + properties['background-attachment']?.important, + properties['background-position']?.important + ].some((important) => important === false), + value: `${properties['background-color']?.value || ''} ${ + properties['background-image']?.value || '' + } ${properties['background-repeat']?.value || ''} ${ + properties['background-repeat']?.value + ? properties['background-attachment']?.value || '' + : '' + } ${ + properties['background-repeat']?.value && properties['background-attachment']?.value + ? properties['background-position']?.value || '' + : '' + }` + .replace(/ /g, '') + .trim() + }; + } + + /** + * Returns flex. + * + * @param properties Properties. + * @returns Property value + */ + public static getFlex(properties: { + [k: string]: ICSSStyleDeclarationPropertyValue; + }): ICSSStyleDeclarationPropertyValue { + if ( + !properties['flex-grow']?.value || + !properties['flex-shrink']?.value || + !properties['flex-basis']?.value + ) { + return null; + } + return { + important: ![ + properties['flex-grow']?.important, + properties['flex-shrink']?.important, + properties['flex-basis']?.important + ].some((important) => important === false), + value: `${properties['flex-grow'].value} ${properties['flex-shrink'].value} ${properties['flex-basis'].value}` + }; + } + + /** + * Returns flex. + * + * @param properties Properties. + * @returns Property value + */ + public static getFont(properties: { + [k: string]: ICSSStyleDeclarationPropertyValue; + }): ICSSStyleDeclarationPropertyValue { + if (!properties['font-family']?.value || !properties['font-size']?.value) { + return null; + } + return { + important: ![ + properties['font-style']?.important, + properties['font-variant']?.important, + properties['font-weight']?.important, + properties['font-stretch']?.important, + properties['font-size']?.important, + properties['line-height']?.important, + properties['font-family']?.important + ].some((important) => important === false), + value: `${properties['font-style'].value || ''} ${properties['font-variant'].value || ''} ${ + properties['font-weight'].value || '' + } ${properties['font-stretch'].value || ''} ${properties['font-size'].value || ''} ${ + properties['line-height'].value || '' + } ${properties['font-family'].value || ''}` + .replace(/ /g, '') + .trim() + }; + } +} diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts index 7c7656d4f..57c94ddd6 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts @@ -1,11 +1,13 @@ import ICSSStyleDeclarationPropertyValue from './ICSSStyleDeclarationPropertyValue'; -import CSSStyleDeclarationPropertyValueParser from './CSSStyleDeclarationPropertyValueParser'; +import CSSStyleDeclarationPropertySetParser from './CSSStyleDeclarationPropertySetParser'; +import CSSStyleDeclarationValueParser from './CSSStyleDeclarationValueParser'; +import CSSStyleDeclarationPropertyGetParser from './CSSStyleDeclarationPropertyGetParser'; /** * Computed this.properties property parser. */ export default class CSSStyleDeclarationPropertyManager { - private properties: { + public properties: { [k: string]: ICSSStyleDeclarationPropertyValue; } = {}; @@ -44,208 +46,43 @@ export default class CSSStyleDeclarationPropertyManager { * @param name Property name. * @returns Property value. */ - public get(name: string): string { + public get(name: string): ICSSStyleDeclarationPropertyValue { + if (this.properties[name]) { + return this.properties[name]; + } + switch (name) { case 'margin': - if (!this.properties['margin-top']?.value) { - return ''; - } - return `${this.properties['margin-top'].value} ${ - this.properties['margin-right']?.value || '' - } ${ - this.properties['margin-top'].value !== this.properties['margin-bottom']?.value - ? this.properties['margin-bottom']?.value || '' - : '' - } ${ - this.properties['margin-right'].value !== this.properties['margin-left']?.value - ? this.properties['margin-left']?.value || '' - : '' - }` - .replace(/ /g, '') - .trim(); + return CSSStyleDeclarationPropertyGetParser.getMargin(this.properties); case 'padding': - if (!this.properties['padding-top']?.value) { - return ''; - } - return `${this.properties['padding-top'].value} ${ - this.properties['padding-right']?.value || '' - } ${ - this.properties['padding-top'].value !== this.properties['padding-bottom']?.value - ? this.properties['padding-bottom']?.value || '' - : '' - } ${ - this.properties['padding-right'].value !== this.properties['padding-left']?.value - ? this.properties['padding-left']?.value || '' - : '' - }` - .replace(/ /g, '') - .trim(); + return CSSStyleDeclarationPropertyGetParser.getPadding(this.properties); case 'border': - if ( - !this.properties['border-top-width']?.value || - this.properties['border-right-width']?.value !== - this.properties['border-top-width']?.value || - this.properties['border-right-style']?.value !== - this.properties['border-top-style']?.value || - this.properties['border-right-color']?.value !== - this.properties['border-top-color']?.value || - this.properties['border-bottom-width']?.value !== - this.properties['border-top-width']?.value || - this.properties['border-bottom-style']?.value !== - this.properties['border-top-style']?.value || - this.properties['border-bottom-color']?.value !== - this.properties['border-top-color']?.value || - this.properties['border-left-width']?.value !== - this.properties['border-top-width']?.value || - this.properties['border-left-style']?.value !== - this.properties['border-top-style']?.value || - this.properties['border-left-color']?.value !== this.properties['border-top-color']?.value - ) { - return ''; - } - return `${this.properties['border-top-width'].value} ${ - this.properties['border-top-style']?.value || '' - } ${this.properties['border-top-color']?.value || ''}` - .replace(/ /g, '') - .trim(); - case 'border-left': - if (!this.properties['border-left-width']?.value) { - return ''; - } - return `${this.properties['border-left-width'].value} ${ - this.properties['border-left-style']?.value || '' - } ${this.properties['border-left-color']?.value || ''}` - .replace(/ /g, '') - .trim(); - case 'border-right': - if (!this.properties['border-right-width']?.value) { - return ''; - } - return `${this.properties['border-right-width'].value} ${ - this.properties['border-right-style']?.value || '' - } ${this.properties['border-right-color']?.value || ''}` - .replace(/ /g, '') - .trim(); + return CSSStyleDeclarationPropertyGetParser.getBorder(this.properties); case 'border-top': - if (!this.properties['border-top-width']?.value) { - return ''; - } - return `${this.properties['border-top-width'].value} ${ - this.properties['border-top-style']?.value || '' - } ${this.properties['border-top-color']?.value || ''}` - .replace(/ /g, '') - .trim(); + return CSSStyleDeclarationPropertyGetParser.getBorderTop(this.properties); + case 'border-right': + return CSSStyleDeclarationPropertyGetParser.getBorderRight(this.properties); case 'border-bottom': - if (!this.properties['border-bottom-width']?.value) { - return ''; - } - return `${this.properties['border-bottom-width'].value} ${ - this.properties['border-bottom-style']?.value || '' - } ${this.properties['border-bottom-color']?.value || ''}` - .replace(/ /g, '') - .trim(); + return CSSStyleDeclarationPropertyGetParser.getBorderBottom(this.properties); + case 'border-left': + return CSSStyleDeclarationPropertyGetParser.getBorderLeft(this.properties); case 'border-color': - if ( - !this.properties['border-top-color']?.value || - this.properties['border-top-color']?.value !== - this.properties['border-right-color']?.value || - this.properties['border-top-color']?.value !== - this.properties['border-bottom-color']?.value || - this.properties['border-top-color']?.value !== this.properties['border-left-color']?.value - ) { - return ''; - } - return this.properties['border-top-color'].value; + return CSSStyleDeclarationPropertyGetParser.getBorderColor(this.properties); case 'border-style': - if ( - !this.properties['border-top-style']?.value || - this.properties['border-top-style']?.value !== - this.properties['border-right-style']?.value || - this.properties['border-top-style']?.value !== - this.properties['border-bottom-style']?.value || - this.properties['border-top-style']?.value !== this.properties['border-left-style']?.value - ) { - return ''; - } - return this.properties['border-top-style'].value; + return CSSStyleDeclarationPropertyGetParser.getBorderStyle(this.properties); case 'border-width': - if ( - !this.properties['border-top-width']?.value || - this.properties['border-top-width']?.value !== - this.properties['border-right-width']?.value || - this.properties['border-top-width']?.value !== - this.properties['border-bottom-width']?.value || - this.properties['border-top-width']?.value !== this.properties['border-left-width']?.value - ) { - return ''; - } - return this.properties['border-top-width'].value; + return CSSStyleDeclarationPropertyGetParser.getBorderWidth(this.properties); case 'border-radius': - if (!this.properties['border-top-left-radius']?.value) { - return ''; - } - return `${this.properties['border-top-left-radius'].value} ${ - this.properties['border-top-right-radius'].value || '' - } ${ - this.properties['border-top-left-radius'].value !== - this.properties['border-bottom-right-radius'].value - ? this.properties['border-bottom-right-radius'].value || '' - : '' - } ${ - this.properties['border-top-right-radius'].value !== - this.properties['border-bottom-left-radius'].value - ? this.properties['border-bottom-left-radius'].value || '' - : '' - }` - .replace(/ /g, '') - .trim(); + return CSSStyleDeclarationPropertyGetParser.getBorderRadius(this.properties); case 'background': - if ( - !this.properties['background-color']?.value && - !this.properties['background-image']?.value - ) { - return ''; - } - return `${this.properties['background-color']?.value || ''} ${ - this.properties['background-image']?.value || '' - } ${this.properties['background-repeat']?.value || ''} ${ - this.properties['background-repeat']?.value - ? this.properties['background-attachment']?.value || '' - : '' - } ${ - this.properties['background-repeat']?.value && - this.properties['background-attachment']?.value - ? this.properties['background-position']?.value || '' - : '' - }` - .replace(/ /g, '') - .trim(); + return CSSStyleDeclarationPropertyGetParser.getBackground(this.properties); case 'flex': - if ( - !this.properties['flex-grow']?.value || - !this.properties['flex-shrink']?.value || - !this.properties['flex-basis']?.value - ) { - return ''; - } - return `${this.properties['flex-grow'].value} ${this.properties['flex-shrink'].value} ${this.properties['flex-basis'].value}`; + return CSSStyleDeclarationPropertyGetParser.getFlex(this.properties); case 'font': - if (this.properties['font']?.value) { - return this.properties['font'].value; - } - if (!this.properties['font-family']?.value) { - return ''; - } - return `${this.properties['font-family'].value} ${ - this.properties['font-size'].value || '' - } ${this.properties['font-style'].value || ''} ${ - this.properties['font-weight'].value || '' - }` - .replace(/ /g, '') - .trim(); + return CSSStyleDeclarationPropertyGetParser.getFont(this.properties); } - return this.properties[name]?.value || ''; + return this.properties[name] || null; } /** @@ -254,6 +91,8 @@ export default class CSSStyleDeclarationPropertyManager { * @param name Property name. */ public remove(name: string): void { + delete this.properties[name]; + switch (name) { case 'border': delete this.properties['border-top-width']; @@ -325,6 +164,15 @@ export default class CSSStyleDeclarationPropertyManager { delete this.properties['flex-shrink']; delete this.properties['flex-basis']; break; + case 'font': + delete this.properties['font-style']; + delete this.properties['font-variant']; + delete this.properties['font-weight']; + delete this.properties['font-stretch']; + delete this.properties['font-size']; + delete this.properties['line-height']; + delete this.properties['font-family']; + break; case 'padding': delete this.properties['padding-top']; delete this.properties['padding-right']; @@ -337,9 +185,6 @@ export default class CSSStyleDeclarationPropertyManager { delete this.properties['margin-bottom']; delete this.properties['margin-left']; break; - default: - delete this.properties[name]; - break; } } @@ -351,197 +196,220 @@ export default class CSSStyleDeclarationPropertyManager { * @param important Important. */ public set(name: string, value: string, important: boolean): void { + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); + + if (globalValue) { + this.remove(name); + this.properties[name] = { + value: globalValue, + important + }; + return; + } + let properties = null; switch (name) { case 'border': - properties = CSSStyleDeclarationPropertyValueParser.getBorder(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBorder(value, important); break; case 'border-top': - properties = CSSStyleDeclarationPropertyValueParser.getBorderTop(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBorderTop(value, important); break; case 'border-right': - properties = CSSStyleDeclarationPropertyValueParser.getBorderRight(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBorderRight(value, important); break; case 'border-bottom': - properties = CSSStyleDeclarationPropertyValueParser.getBorderBottom(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBorderBottom(value, important); break; case 'border-left': - properties = CSSStyleDeclarationPropertyValueParser.getBorderLeft(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBorderLeft(value, important); break; case 'border-width': - properties = CSSStyleDeclarationPropertyValueParser.getBorderWidth(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBorderWidth(value, important); break; case 'border-style': - properties = CSSStyleDeclarationPropertyValueParser.getBorderStyle(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBorderStyle(value, important); break; case 'border-color': - properties = CSSStyleDeclarationPropertyValueParser.getBorderColor(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBorderColor(value, important); break; case 'border-top-width': - properties = CSSStyleDeclarationPropertyValueParser.getBorderTopWidth(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBorderTopWidth(value, important); break; case 'border-right-width': - properties = CSSStyleDeclarationPropertyValueParser.getBorderRightWidth(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBorderRightWidth(value, important); break; case 'border-bottom-width': - properties = CSSStyleDeclarationPropertyValueParser.getBorderBottomWidth(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBorderBottomWidth(value, important); break; case 'border-left-width': - properties = CSSStyleDeclarationPropertyValueParser.getBorderLeftWidth(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBorderLeftWidth(value, important); break; case 'border-top-color': - properties = CSSStyleDeclarationPropertyValueParser.getBorderTopColor(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBorderTopColor(value, important); break; case 'border-right-color': - properties = CSSStyleDeclarationPropertyValueParser.getBorderRightColor(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBorderRightColor(value, important); break; case 'border-bottom-color': - properties = CSSStyleDeclarationPropertyValueParser.getBorderBottomColor(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBorderBottomColor(value, important); break; case 'border-left-color': - properties = CSSStyleDeclarationPropertyValueParser.getBorderLeftColor(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBorderLeftColor(value, important); break; case 'border-top-style': - properties = CSSStyleDeclarationPropertyValueParser.getBorderTopStyle(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBorderTopStyle(value, important); break; case 'border-right-style': - properties = CSSStyleDeclarationPropertyValueParser.getBorderRightStyle(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBorderRightStyle(value, important); break; case 'border-bottom-style': - properties = CSSStyleDeclarationPropertyValueParser.getBorderBottomStyle(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBorderBottomStyle(value, important); break; case 'border-left-style': - properties = CSSStyleDeclarationPropertyValueParser.getBorderLeftStyle(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBorderLeftStyle(value, important); break; case 'border-radius': - properties = CSSStyleDeclarationPropertyValueParser.getBorderRadius(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBorderRadius(value, important); break; case 'border-top-left-radius': - properties = CSSStyleDeclarationPropertyValueParser.getBorderTopLeftRadius( - value, - important - ); + properties = CSSStyleDeclarationPropertySetParser.getBorderTopLeftRadius(value, important); break; case 'border-top-right-radius': - properties = CSSStyleDeclarationPropertyValueParser.getBorderTopRightRadius( - value, - important - ); + properties = CSSStyleDeclarationPropertySetParser.getBorderTopRightRadius(value, important); break; case 'border-bottom-right-radius': - properties = CSSStyleDeclarationPropertyValueParser.getBorderBottomRightRadius( + properties = CSSStyleDeclarationPropertySetParser.getBorderBottomRightRadius( value, important ); break; case 'border-bottom-right-radius': - properties = CSSStyleDeclarationPropertyValueParser.getBorderBottomLeftRadius( + properties = CSSStyleDeclarationPropertySetParser.getBorderBottomLeftRadius( value, important ); break; case 'border-collapse': - properties = CSSStyleDeclarationPropertyValueParser.getBorderCollapse(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBorderCollapse(value, important); break; case 'clear': - properties = CSSStyleDeclarationPropertyValueParser.getClear(value, important); + properties = CSSStyleDeclarationPropertySetParser.getClear(value, important); break; case 'clip': - properties = CSSStyleDeclarationPropertyValueParser.getClip(value, important); + properties = CSSStyleDeclarationPropertySetParser.getClip(value, important); break; case 'css-float': - properties = CSSStyleDeclarationPropertyValueParser.getCSSFloat(value, important); + properties = CSSStyleDeclarationPropertySetParser.getCSSFloat(value, important); break; case 'float': - properties = CSSStyleDeclarationPropertyValueParser.getFloat(value, important); + properties = CSSStyleDeclarationPropertySetParser.getFloat(value, important); break; case 'flex': - properties = CSSStyleDeclarationPropertyValueParser.getFlex(value, important); + properties = CSSStyleDeclarationPropertySetParser.getFlex(value, important); break; case 'flex-shrink': - properties = CSSStyleDeclarationPropertyValueParser.getFlexShrink(value, important); + properties = CSSStyleDeclarationPropertySetParser.getFlexShrink(value, important); break; case 'flex-grow': - properties = CSSStyleDeclarationPropertyValueParser.getFlexGrow(value, important); + properties = CSSStyleDeclarationPropertySetParser.getFlexGrow(value, important); break; case 'flex-basis': - properties = CSSStyleDeclarationPropertyValueParser.getFlexBasis(value, important); + properties = CSSStyleDeclarationPropertySetParser.getFlexBasis(value, important); break; case 'padding': - properties = CSSStyleDeclarationPropertyValueParser.getPadding(value, important); + properties = CSSStyleDeclarationPropertySetParser.getPadding(value, important); break; case 'padding-top': - properties = CSSStyleDeclarationPropertyValueParser.getPaddingTop(value, important); + properties = CSSStyleDeclarationPropertySetParser.getPaddingTop(value, important); break; case 'padding-bottom': - properties = CSSStyleDeclarationPropertyValueParser.getPaddingBottom(value, important); + properties = CSSStyleDeclarationPropertySetParser.getPaddingBottom(value, important); break; case 'padding-left': - properties = CSSStyleDeclarationPropertyValueParser.getPaddingLeft(value, important); + properties = CSSStyleDeclarationPropertySetParser.getPaddingLeft(value, important); break; case 'padding-right': - properties = CSSStyleDeclarationPropertyValueParser.getPaddingRight(value, important); + properties = CSSStyleDeclarationPropertySetParser.getPaddingRight(value, important); break; case 'margin': - properties = CSSStyleDeclarationPropertyValueParser.getMargin(value, important); + properties = CSSStyleDeclarationPropertySetParser.getMargin(value, important); break; case 'margin-top': - properties = CSSStyleDeclarationPropertyValueParser.getMarginTop(value, important); + properties = CSSStyleDeclarationPropertySetParser.getMarginTop(value, important); break; case 'margin-bottom': - properties = CSSStyleDeclarationPropertyValueParser.getMarginBottom(value, important); + properties = CSSStyleDeclarationPropertySetParser.getMarginBottom(value, important); break; case 'margin-left': - properties = CSSStyleDeclarationPropertyValueParser.getMarginLeft(value, important); + properties = CSSStyleDeclarationPropertySetParser.getMarginLeft(value, important); break; case 'margin-right': - properties = CSSStyleDeclarationPropertyValueParser.getMarginRight(value, important); + properties = CSSStyleDeclarationPropertySetParser.getMarginRight(value, important); break; case 'background': - properties = CSSStyleDeclarationPropertyValueParser.getBackground(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBackground(value, important); break; case 'background-image': - properties = CSSStyleDeclarationPropertyValueParser.getBackgroundImage(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBackgroundImage(value, important); break; case 'background-color': - properties = CSSStyleDeclarationPropertyValueParser.getBackgroundColor(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBackgroundColor(value, important); break; case 'background-repeat': - properties = CSSStyleDeclarationPropertyValueParser.getBackgroundRepeat(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBackgroundRepeat(value, important); break; case 'background-attachment': - properties = CSSStyleDeclarationPropertyValueParser.getBackgroundAttachment( - value, - important - ); + properties = CSSStyleDeclarationPropertySetParser.getBackgroundAttachment(value, important); break; case 'background-position': - properties = CSSStyleDeclarationPropertyValueParser.getBackgroundPosition(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBackgroundPosition(value, important); break; case 'top': - properties = CSSStyleDeclarationPropertyValueParser.getTop(value, important); + properties = CSSStyleDeclarationPropertySetParser.getTop(value, important); break; case 'right': - properties = CSSStyleDeclarationPropertyValueParser.getRight(value, important); + properties = CSSStyleDeclarationPropertySetParser.getRight(value, important); break; case 'bottom': - properties = CSSStyleDeclarationPropertyValueParser.getBottom(value, important); + properties = CSSStyleDeclarationPropertySetParser.getBottom(value, important); break; case 'left': - properties = CSSStyleDeclarationPropertyValueParser.getLeft(value, important); + properties = CSSStyleDeclarationPropertySetParser.getLeft(value, important); break; case 'font': - properties = CSSStyleDeclarationPropertyValueParser.getFont(value, important); + properties = CSSStyleDeclarationPropertySetParser.getFont(value, important); + break; + case 'font-style': + properties = CSSStyleDeclarationPropertySetParser.getFontStyle(value, important); + break; + case 'font-variant': + properties = CSSStyleDeclarationPropertySetParser.getFontVariant(value, important); + break; + case 'font-weight': + properties = CSSStyleDeclarationPropertySetParser.getFontWeight(value, important); + break; + case 'font-stretch': + properties = CSSStyleDeclarationPropertySetParser.getFontStretch(value, important); + break; + case 'font-size': + properties = CSSStyleDeclarationPropertySetParser.getFontSize(value, important); + break; + case 'line-height': + properties = CSSStyleDeclarationPropertySetParser.getLineHeight(value, important); + break; + case 'font-family': + properties = CSSStyleDeclarationPropertySetParser.getFontFamily(value, important); break; case 'font-size': - properties = CSSStyleDeclarationPropertyValueParser.getFontSize(value, important); + properties = CSSStyleDeclarationPropertySetParser.getFontSize(value, important); break; case 'color': - properties = CSSStyleDeclarationPropertyValueParser.getColor(value, important); + properties = CSSStyleDeclarationPropertySetParser.getColor(value, important); break; case 'flood-color': - properties = CSSStyleDeclarationPropertyValueParser.getFloodColor(value, important); + properties = CSSStyleDeclarationPropertySetParser.getFloodColor(value, important); break; default: properties = value @@ -554,4 +422,80 @@ export default class CSSStyleDeclarationPropertyManager { Object.assign(this.properties, properties); } + + /** + * Returns a clone. + * + * @returns Clone. + */ + public clone(): CSSStyleDeclarationPropertyManager { + const _class = this.constructor; + const clone: CSSStyleDeclarationPropertyManager = new _class(); + + clone.properties = JSON.parse(JSON.stringify(this.properties)); + + return clone; + } + + /** + * Returns size. + * + * @returns Size. + */ + public size(): number { + return Object.keys(this.properties).length; + } + + /** + * Returns property name. + * + * @param index Index. + * @returns Property name. + */ + public item(index: number): string { + return Object.keys(this.properties)[index] || ''; + } + + /** + * Converts properties to string. + * + * @returns String. + */ + public toString(): string { + const clone = this.clone(); + const groupProperties = { + margin: clone.get('margin'), + padding: clone.get('padding'), + border: clone.get('border'), + 'border-top': clone.get('border-top'), + 'border-right': clone.get('border-right'), + 'border-bottom': clone.get('border-bottom'), + 'border-left': clone.get('border-left'), + 'border-color': clone.get('border-color'), + 'border-style': clone.get('border-style'), + 'border-width': clone.get('border-width'), + 'border-radius': clone.get('border-radius'), + background: clone.get('background'), + flex: clone.get('flex'), + font: clone.get('font') + }; + + let result = ''; + + for (const name of Object.keys(groupProperties)) { + if (groupProperties[name]) { + result += `${name}: ${groupProperties[name].values}${ + groupProperties[name].important ? ' !important' : '' + }; `; + clone.remove(name); + } + } + + for (const name of Object.keys(clone.properties)) { + const property = clone.properties[name]; + result += `${name}: ${property.value}${property.important ? ' !important' : ''}; `; + } + + return result; + } } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValueParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts similarity index 85% rename from packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValueParser.ts rename to packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts index 72aabd6a7..3e4a977a4 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValueParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts @@ -14,35 +14,17 @@ const BORDER_STYLE = [ 'inset', 'outset' ]; -const BORDER_WIDTH = ['thin', 'medium', 'thick', 'inherit', 'initial', 'unset', 'revert']; -const BORDER_COLLAPSE = ['separate', 'collapse', 'initial', 'inherit']; -const BACKGROUND_REPEAT = ['repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'inherit']; -const BACKGROUND_ATTACHMENT = ['scroll', 'fixed', 'inherit']; +const BORDER_WIDTH = ['thin', 'medium', 'thick']; +const BORDER_COLLAPSE = ['separate', 'collapse']; +const BACKGROUND_REPEAT = ['repeat', 'repeat-x', 'repeat-y', 'no-repeat']; +const BACKGROUND_ATTACHMENT = ['scroll', 'fixed']; const BACKGROUND_POSITION = ['top', 'center', 'bottom', 'left', 'right']; -const FLEX_BASIS = [ - 'auto', - 'fill', - 'max-content', - 'min-content', - 'fit-content', - 'content', - 'inherit', - 'initial', - 'revert', - 'unset' -]; -const CLEAR = ['none', 'left', 'right', 'both', 'inherit']; -const FLOAT = ['none', 'left', 'right', 'inherit']; -const SYSTEM_FONT = [ - 'caption', - 'icon', - 'menu', - 'message-box', - 'small-caption', - 'status-bar', - 'inherit' -]; +const FLEX_BASIS = ['auto', 'fill', 'max-content', 'min-content', 'fit-content', 'content']; +const CLEAR = ['none', 'left', 'right', 'both']; +const FLOAT = ['none', 'left', 'right']; +const SYSTEM_FONT = ['caption', 'icon', 'menu', 'message-box', 'small-caption', 'status-bar']; const FONT_WEIGHT = ['normal', 'bold', 'bolder', 'lighter']; +const FONT_STYLE = ['normal', 'italic', 'oblique']; const FONT_SIZE = [ 'xx-small', 'x-small', @@ -53,17 +35,24 @@ const FONT_SIZE = [ 'xx-large', 'xxx-large', 'smaller', - 'larger', - 'inherit', - 'initial', - 'revert', - 'unset' + 'larger' +]; +const FONT_STRETCH = [ + 'ultra-condensed', + 'extra-condensed', + 'condensed', + 'semi-condensed', + 'normal', + 'semi-expanded', + 'expanded', + 'extra-expanded', + 'ultra-expanded' ]; /** * Computed style property parser. */ -export default class CSSStyleDeclarationPropertyValueParser { +export default class CSSStyleDeclarationPropertySetParser { /** * Returns border collapse. * @@ -1263,14 +1252,105 @@ export default class CSSStyleDeclarationPropertyValueParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const lowerValue = value.toLowerCase(); + + if (SYSTEM_FONT.includes(lowerValue)) { + return { font: { value: lowerValue, important } }; + } + const parts = value.split(/ +/); - let font; - let fontFamily; - let fontSize; - let fontStyle; - let fontVariant; - let fontWeight; - let lineHeight; + + if (parts.length > 0 && this.getFontStyle(parts[0], important)) { + if (parts[1] && parts[0] === 'oblique' && parts[1].endsWith('deg')) { + parts[0] += ' ' + parts[1]; + parts.splice(1, 1); + } + } else { + parts.splice(0, 0, ''); + } + + if (parts.length <= 1 || !this.getFontVariant(parts[1], important)) { + parts.splice(1, 0, ''); + } + + if (parts.length <= 2 || !this.getFontWeight(parts[2], important)) { + parts.splice(2, 0, ''); + } + + if (parts.length <= 3 || !this.getFontStretch(parts[3], important)) { + parts.splice(3, 0, ''); + } + + if (parts.length <= 5 || !this.getLineHeight(parts[5], important)) { + parts.splice(5, 0, ''); + } + + const fontStyle = parts[0] ? this.getFontStyle(parts[0], important) : ''; + const fontVariant = parts[0] ? this.getFontVariant(parts[1], important) : ''; + const fontWeight = parts[2] ? this.getFontWeight(parts[2], important) : ''; + const fontStretch = parts[3] ? this.getFontStretch(parts[3], important) : ''; + const fontSize = parts[4] ? this.getFontStretch(parts[4], important) : ''; + const lineHeight = parts[5] ? this.getLineHeight(parts[5], important) : ''; + const fontFamily = parts[6] ? this.getFontFamily(parts.slice(6).join(' '), important) : ''; + + const properties = {}; + + if (fontStyle) { + Object.assign(properties, fontStyle); + } + if (fontVariant) { + Object.assign(properties, fontVariant); + } + if (fontWeight) { + Object.assign(properties, fontWeight); + } + if (fontStretch) { + Object.assign(properties, fontStretch); + } + if (fontSize) { + Object.assign(properties, fontSize); + } + if (lineHeight) { + Object.assign(properties, lineHeight); + } + if (fontFamily) { + Object.assign(properties, fontFamily); + } + + return fontSize && + fontFamily && + fontStyle !== null && + fontVariant !== null && + fontWeight !== null && + fontStretch !== null && + lineHeight !== null + ? properties + : null; + } + + /** + * Returns font style. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getFontStyle( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const lowerValue = value.toLowerCase(); + if (FONT_STYLE.includes(lowerValue)) { + return { fontStyle: { value: lowerValue, important } }; + } + const parts = value.split(/ +/); + if (parts.length === 2 && parts[0] === 'oblique') { + const degree = CSSStyleDeclarationValueParser.getDegree(parts[1]); + return degree ? { fontStyle: { value: lowerValue, important } } : null; + } + return null; } /** @@ -1292,6 +1372,27 @@ export default class CSSStyleDeclarationPropertyValueParser { : null; } + /** + * Returns font strech. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getFontStretch( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const lowerValue = value.toLowerCase(); + if (FONT_STRETCH.includes(lowerValue)) { + return { 'font-stretch': { value: lowerValue, important } }; + } + const percentage = CSSStyleDeclarationValueParser.getPercentage(value); + return percentage ? { 'font-stretch': { value: percentage, important } } : null; + } + /** * Returns font weight. * @@ -1309,10 +1410,8 @@ export default class CSSStyleDeclarationPropertyValueParser { if (FONT_WEIGHT.includes(lowerValue)) { return { 'font-weight': { value: lowerValue, important } }; } - const measurement = - CSSStyleDeclarationValueParser.getLength(value) || - CSSStyleDeclarationValueParser.getPercentage(value); - return measurement ? { 'font-size': { value: measurement, important } } : null; + const integer = CSSStyleDeclarationValueParser.getInteger(value); + return integer ? { 'font-weight': { value: integer, important } } : null; } /** @@ -1332,7 +1431,65 @@ export default class CSSStyleDeclarationPropertyValueParser { if (FONT_SIZE.includes(lowerValue)) { return { 'font-size': { value: lowerValue, important } }; } - const measurement = CSSStyleDeclarationValueParser.getLengthOrPercentage(value); + const measurement = CSSStyleDeclarationValueParser.getMeasurement(value); return measurement ? { 'font-size': { value: measurement, important } } : null; } + + /** + * Returns line height. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getLineHeight( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + if (value.toLowerCase() === 'normal') { + return { 'line-height': { value: 'normal', important } }; + } + const lineHeight = + CSSStyleDeclarationValueParser.getInteger(value) || + CSSStyleDeclarationValueParser.getMeasurement(value); + return lineHeight ? { 'line-height': { value: lineHeight, important } } : null; + } + + /** + * Returns font family. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getFontFamily( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const parts = value.split(','); + let parsedValue = ''; + for (let i = 0, max = parts.length; i < max; i++) { + const trimmedPart = parts[i].trim().replace(/[']/g, '"'); + if (!trimmedPart) { + return null; + } + if (i > 0) { + parsedValue += ', '; + } + parsedValue += trimmedPart; + } + if (!parsedValue) { + return null; + } + return { + 'font-family': { + important, + value: parsedValue + } + }; + } } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStyleString.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStyleString.ts deleted file mode 100644 index 2852ca413..000000000 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStyleString.ts +++ /dev/null @@ -1,90 +0,0 @@ -import IElement from '../../../nodes/element/IElement'; -import ICSSStyleDeclarationProperty from './ICSSStyleDeclarationPropertyValue'; -import CSSStyleDeclarationPropertyManager from './CSSStyleDeclarationPropertyManager'; - -/** - * CSS Style Declaration utility - */ -export default class CSSStyleDeclarationStyleString { - /** - * Returns a style from a string. - * - * @param styleString Style string (e.g. "border: 2px solid red; font-size: 12px;"). - * @returns Style. - */ - public static getStyleProperties(styleString: string): { - [k: string]: ICSSStyleDeclarationProperty; - } { - const style = {}; - const parts = styleString.split(';'); - const writer = new CSSStyleDeclarationPropertyManager(style); - - for (const part of parts) { - if (part) { - const [name, value]: string[] = part.trim().split(':'); - if (value) { - const trimmedName = name.trim(); - const trimmedValue = value.trim(); - if (trimmedName && trimmedValue) { - const important = trimmedValue.endsWith(' !important'); - const valueWithoutImportant = trimmedValue.replace(' !important', ''); - - if (valueWithoutImportant) { - writer.set({ - name: trimmedName, - value: valueWithoutImportant, - important - }); - } - } - } - } - } - - return style; - } - - /** - * Returns element style properties. - * - * @param element Element. - * @param [computed] Computed. - * @returns Element style properties. - */ - public static getElementStyleProperties( - element: IElement, - computed: boolean - ): { - [k: string]: ICSSStyleDeclarationProperty; - } { - if (computed) { - // TODO: Add logic for style sheets - } - if (element['_attributes']['style'] && element['_attributes']['style'].value) { - return this.getStyleProperties(element['_attributes']['style'].value); - } - - return {}; - } - - /** - * Returns a style string. - * - * @param style Style. - * @returns Styles as string. - */ - public static getStyleString(style: { [k: string]: ICSSStyleDeclarationProperty }): string { - let styleString = ''; - - for (const property of Object.values(style)) { - if (styleString) { - styleString += ' '; - } - styleString += `${property.name}: ${property.value}${ - property.important ? ' !important' : '' - };`; - } - - return styleString; - } -} diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts index 214045e76..f9e0a70ed 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts @@ -3,7 +3,9 @@ const COLOR_REGEXP = const LENGTH_REGEXP = /^(0|[-+]?[0-9]*\.?[0-9]+(in|cm|em|mm|pt|pc|px|ex|rem|vh|vw|ch))$/; const PERCENTAGE_REGEXP = /^[-+]?[0-9]*\.?[0-9]+%$/; +const DEGREE_REGEXP = /^[0-9]+deg$/; const URL_REGEXP = /^url\(\s*([^)]*)\s*\)$/; +const GLOBALS = ['inherit', 'initial', 'unset', 'revert']; /** * Style declaration value parser. @@ -24,6 +26,7 @@ export default class CSSStyleDeclarationValueParser { } return null; } + /** * Returns percentance. * @@ -41,13 +44,19 @@ export default class CSSStyleDeclarationValueParser { } /** - * Returns length or percentage. + * Returns degree. * * @param value Value. * @returns Parsed value. */ - public static getLengthOrPercentage(value: string): string { - return this.getLength(value) || this.getPercentage(value); + public static getDegree(value: string): string { + if (value === '0') { + return '0deg'; + } + if (DEGREE_REGEXP.test(value)) { + return value; + } + return null; } /** @@ -57,16 +66,6 @@ export default class CSSStyleDeclarationValueParser { * @returns Parsed value. */ public static getMeasurement(value: string): string { - const lowerValue = value.toLowerCase(); - if ( - lowerValue === 'inherit' || - lowerValue === 'initial' || - lowerValue === 'revert' || - lowerValue === 'unset' - ) { - return lowerValue; - } - return this.getLength(value) || this.getPercentage(value); } @@ -164,4 +163,15 @@ export default class CSSStyleDeclarationValueParser { return value; } + + /** + * Returns global. + * + * @param value Value. + * @returns Parsed value. + */ + public static getGlobal(value: string): string { + const lowerValue = value.toLowerCase(); + return GLOBALS.includes(lowerValue) ? lowerValue : null; + } } diff --git a/packages/happy-dom/src/css/declaration/utilities/ICSSStyleDeclarationPropertyValue.ts b/packages/happy-dom/src/css/declaration/utilities/ICSSStyleDeclarationPropertyValue.ts index cbcfc9b7d..ad636ebc3 100644 --- a/packages/happy-dom/src/css/declaration/utilities/ICSSStyleDeclarationPropertyValue.ts +++ b/packages/happy-dom/src/css/declaration/utilities/ICSSStyleDeclarationPropertyValue.ts @@ -1,4 +1,4 @@ export default interface ICSSStyleDeclarationPropertyValue { - value: string; - important: boolean; + readonly value: string; + readonly important: boolean; }