From 4ca836be649cbf8faa463381bfa60cfd8350012a Mon Sep 17 00:00:00 2001 From: David Ortner Date: Thu, 1 Sep 2022 00:57:05 +0200 Subject: [PATCH] #344@trivial: Continues on CSSStyleDeclaration. --- .../CSSStyleDeclarationPropertyGetParser.ts | 7 +- .../CSSStyleDeclarationPropertyManager.ts | 33 +- .../CSSStyleDeclarationPropertySetParser.ts | 365 +++++++++++++++++- .../CSSStyleDeclarationValueParser.ts | 15 + 4 files changed, 386 insertions(+), 34 deletions(-) diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts index 25f623f14..3dfc48f21 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts @@ -51,7 +51,12 @@ export default class CSSStyleDeclarationPropertyGetParser { !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 + properties['border-top-width']?.value !== properties['border-left-width']?.value || + !properties['border-image-source']?.value || + !properties['border-image-slice']?.value || + !properties['border-image-width']?.value || + !properties['border-image-outset']?.value || + !properties['border-image-repeat']?.value ) { return null; } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts index eca52fddc..12c9fe919 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts @@ -198,18 +198,6 @@ 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 - }; - this.definedPropertyNames[name] = true; - return; - } - let properties = null; switch (name) { @@ -237,6 +225,24 @@ export default class CSSStyleDeclarationPropertyManager { case 'border-color': properties = CSSStyleDeclarationPropertySetParser.getBorderColor(value, important); break; + case 'border-image': + properties = CSSStyleDeclarationPropertySetParser.getBorderImage(value, important); + break; + case 'border-image-source': + properties = CSSStyleDeclarationPropertySetParser.getBorderImageSource(value, important); + break; + case 'border-image-slice': + properties = CSSStyleDeclarationPropertySetParser.getBorderImageSlice(value, important); + break; + case 'border-image-width': + properties = CSSStyleDeclarationPropertySetParser.getBorderImageWidth(value, important); + break; + case 'border-image-outset': + properties = CSSStyleDeclarationPropertySetParser.getBorderImageOutset(value, important); + break; + case 'border-image-repeat': + properties = CSSStyleDeclarationPropertySetParser.getBorderImageRepeat(value, important); + break; case 'border-top-width': properties = CSSStyleDeclarationPropertySetParser.getBorderTopWidth(value, important); break; @@ -421,9 +427,10 @@ export default class CSSStyleDeclarationPropertyManager { properties = CSSStyleDeclarationPropertySetParser.getFloodColor(value, important); break; default: + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); properties = value ? { - [name]: { value, important } + [name]: { value: globalValue ? globalValue : value, important } } : null; break; diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts index 6fbfb1e3f..6852cc1b7 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts @@ -79,6 +79,7 @@ const DISPLAY = [ 'table-row', 'list-item' ]; +const BORDER_IMAGE_REPEAT = ['stretch', 'repeat', 'round', 'space']; /** * Computed style property parser. @@ -98,7 +99,10 @@ export default class CSSStyleDeclarationPropertySetParser { [key: string]: ICSSStyleDeclarationPropertyValue; } { const lowerValue = value.toLowerCase(); - if (BORDER_COLLAPSE.includes(lowerValue)) { + if ( + CSSStyleDeclarationValueParser.getGlobal(lowerValue) || + BORDER_COLLAPSE.includes(lowerValue) + ) { return { 'border-collapse': { value: lowerValue, important } }; } return null; @@ -118,7 +122,10 @@ export default class CSSStyleDeclarationPropertySetParser { [key: string]: ICSSStyleDeclarationPropertyValue; } { const lowerValue = value.toLowerCase(); - if (BACKGROUND_REPEAT.includes(lowerValue)) { + if ( + CSSStyleDeclarationValueParser.getGlobal(lowerValue) || + BACKGROUND_REPEAT.includes(lowerValue) + ) { return { 'background-repeat': { value: lowerValue, important } }; } return null; @@ -138,7 +145,10 @@ export default class CSSStyleDeclarationPropertySetParser { [key: string]: ICSSStyleDeclarationPropertyValue; } { const lowerValue = value.toLowerCase(); - if (BACKGROUND_ATTACHMENT.includes(lowerValue)) { + if ( + CSSStyleDeclarationValueParser.getGlobal(lowerValue) || + BACKGROUND_ATTACHMENT.includes(lowerValue) + ) { return { 'background-attachment': { value: lowerValue, important } }; } return null; @@ -163,6 +173,10 @@ export default class CSSStyleDeclarationPropertySetParser { if (!value) { return null; } + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); + if (globalValue) { + return { 'background-position': { value: globalValue, important } }; + } const parts = value.split(/\s+/); if (parts.length > 2 || parts.length < 1) { return null; @@ -215,7 +229,7 @@ export default class CSSStyleDeclarationPropertySetParser { [key: string]: ICSSStyleDeclarationPropertyValue; } { const lowerValue = value.toLowerCase(); - if (DISPLAY.includes(lowerValue)) { + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || DISPLAY.includes(lowerValue)) { return { display: { value: lowerValue, important } }; } return null; @@ -235,7 +249,11 @@ export default class CSSStyleDeclarationPropertySetParser { [key: string]: ICSSStyleDeclarationPropertyValue; } { const lowerValue = value.toLowerCase(); - if (lowerValue === 'ltr' || lowerValue === 'rtl') { + if ( + CSSStyleDeclarationValueParser.getGlobal(lowerValue) || + lowerValue === 'ltr' || + lowerValue === 'rtl' + ) { return { direction: { value: lowerValue, important } }; } return null; @@ -254,7 +272,9 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const parsedValue = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + const parsedValue = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); return parsedValue ? { top: { value: parsedValue, important } } : null; } @@ -271,7 +291,9 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const parsedValue = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + const parsedValue = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); return parsedValue ? { right: { value: parsedValue, important } } : null; } @@ -288,7 +310,9 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const parsedValue = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + const parsedValue = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); return parsedValue ? { bottom: { value: parsedValue, important } } : null; } @@ -305,7 +329,9 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const parsedValue = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + const parsedValue = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); return parsedValue ? { left: { value: parsedValue, important } } : null; } @@ -323,7 +349,7 @@ export default class CSSStyleDeclarationPropertySetParser { [key: string]: ICSSStyleDeclarationPropertyValue; } { const lowerValue = value.toLowerCase(); - if (CLEAR.includes(lowerValue)) { + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || CLEAR.includes(lowerValue)) { return { clear: { value: lowerValue, important } }; } return null; @@ -346,7 +372,12 @@ export default class CSSStyleDeclarationPropertySetParser { [key: string]: ICSSStyleDeclarationPropertyValue; } { const lowerValue = value.toLowerCase(); - if (lowerValue === 'auto' || lowerValue === 'initial' || lowerValue === 'inherit') { + if ( + CSSStyleDeclarationValueParser.getGlobal(lowerValue) || + lowerValue === 'auto' || + lowerValue === 'initial' || + lowerValue === 'inherit' + ) { return { clip: { value: lowerValue, important } }; } const matches = lowerValue.match(RECT_REGEXP); @@ -379,7 +410,7 @@ export default class CSSStyleDeclarationPropertySetParser { [key: string]: ICSSStyleDeclarationPropertyValue; } { const lowerValue = value.toLowerCase(); - if (FLOAT.includes(lowerValue)) { + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || FLOAT.includes(lowerValue)) { return { float: { value: lowerValue, important } }; } return null; @@ -398,7 +429,8 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const float = this.getFloat(value, important); + const float = + CSSStyleDeclarationValueParser.getGlobal(value) || this.getFloat(value, important); return float ? { 'css-float': float['float'] } : null; } @@ -413,11 +445,15 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); const parts = value.split(/ +/); - const borderWidth = parts[0] ? this.getBorderWidth(parts[0], important) : ''; - const borderStyle = parts[1] ? this.getBorderStyle(parts[1], important) : ''; - const borderColor = parts[2] ? this.getBorderColor(parts[2], important) : ''; - const properties = {}; + const borderWidth = + globalValue || parts[0] ? this.getBorderWidth(globalValue || parts[0], important) : ''; + const borderStyle = + globalValue || parts[1] ? this.getBorderStyle(globalValue || parts[1], important) : ''; + const borderColor = + globalValue || parts[2] ? this.getBorderColor(globalValue || parts[2], important) : ''; + const properties = this.getBorderImage('initial', important); if (borderWidth) { Object.assign(properties, borderWidth); @@ -448,7 +484,7 @@ export default class CSSStyleDeclarationPropertySetParser { [key: string]: ICSSStyleDeclarationPropertyValue; } { const lowerValue = value.toLowerCase(); - if (BORDER_WIDTH.includes(lowerValue)) { + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || BORDER_WIDTH.includes(lowerValue)) { return { 'border-top-width': { value: lowerValue, important }, 'border-right-width': { value: lowerValue, important }, @@ -480,7 +516,7 @@ export default class CSSStyleDeclarationPropertySetParser { [key: string]: ICSSStyleDeclarationPropertyValue; } { const lowerValue = value.toLowerCase(); - if (BORDER_STYLE.includes(lowerValue)) { + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || BORDER_STYLE.includes(lowerValue)) { return { 'border-top-style': { value: lowerValue, important }, 'border-right-style': { value: lowerValue, important }, @@ -504,7 +540,9 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const color = CSSStyleDeclarationValueParser.getColor(value); + const color = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getColor(value); return color ? { 'border-top-color': { value: color, important }, @@ -515,6 +553,293 @@ export default class CSSStyleDeclarationPropertySetParser { : null; } + /** + * Returns border image. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBorderImage( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); + + if (globalValue) { + return { + 'border-image-source': { important, value: globalValue }, + 'border-image-slice': { important, value: globalValue }, + 'border-image-width': { important, value: globalValue }, + 'border-image-outset': { important, value: globalValue }, + 'border-image-repeat': { important, value: globalValue } + }; + } + + const parts = value.toLowerCase().split(/ +/); + const borderImageSource = parts[0] ? this.getBorderImageSource(parts[0], important) : ''; + const repeatValue = []; + + parts.splice(0, 1); + + for (let i = 0; i < 2; i++) { + if (BORDER_IMAGE_REPEAT.includes(parts[parts.length - 1])) { + repeatValue.push(parts[parts.length - 1]); + parts.splice(parts.length - 1, 1); + } + } + + const borderImageRepeat = + repeatValue.length > 0 ? this.getBorderImageRepeat(repeatValue.join(' '), important) : ''; + const measurements = parts.join('').split('/'); + const borderImageSlice = measurements[0] + ? this.getBorderImageSlice(measurements[0], important) + : ''; + const borderImageWidth = measurements[1] + ? this.getBorderImageWidth(measurements[1], important) + : ''; + const borderImageOutset = measurements[2] + ? this.getBorderImageOutset(measurements[2], important) + : ''; + + const properties = {}; + + if (borderImageSource) { + Object.assign(properties, borderImageSource); + } + + if (borderImageSlice) { + Object.assign(properties, borderImageSlice); + } + + if (borderImageWidth) { + Object.assign(properties, borderImageWidth); + } + + if (borderImageOutset) { + Object.assign(properties, borderImageOutset); + } + + if (borderImageRepeat) { + Object.assign(properties, borderImageRepeat); + } + + return borderImageSource && + borderImageRepeat !== null && + borderImageSlice !== null && + borderImageWidth !== null && + borderImageOutset !== null + ? properties + : null; + } + + /** + * Returns border source. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBorderImageSource( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const lowerValue = value.toLowerCase(); + + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || lowerValue === 'none') { + return { + 'border-image-source': { + important, + value: 'none' + } + }; + } + + return { + 'border-image-source': { + important, + value: + CSSStyleDeclarationValueParser.getURL(value) || + CSSStyleDeclarationValueParser.getGradient(value) + } + }; + } + + /** + * Returns border slice. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBorderImageSlice( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const lowerValue = value.toLowerCase(); + + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue)) { + return { + 'border-image-slice': { + important, + value: lowerValue + } + }; + } + + const parts = lowerValue.split(/ +/); + + for (const part of parts) { + if ( + part !== 'fill' && + !CSSStyleDeclarationValueParser.getPercentage(part) && + !CSSStyleDeclarationValueParser.getInteger(part) + ) { + return null; + } + } + + return { + 'border-image-slice': { + important, + value: lowerValue + } + }; + } + + /** + * Returns border width. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBorderImageWidth( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const lowerValue = value.toLowerCase(); + + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue)) { + return { + 'border-image-width': { + important, + value: lowerValue + } + }; + } + + const parts = lowerValue.split(/ +/); + + for (const part of parts) { + if (!CSSStyleDeclarationValueParser.getMeasurementOrAuto(part)) { + return null; + } + } + + return { + 'border-image-width': { + important, + value + } + }; + } + + /** + * Returns border outset. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBorderImageOutset( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const lowerValue = value.toLowerCase(); + + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue)) { + return { + 'border-image-outset': { + important, + value: lowerValue + } + }; + } + + const parts = value.split(/ +/); + + for (const part of parts) { + if ( + !CSSStyleDeclarationValueParser.getLength(part) && + !CSSStyleDeclarationValueParser.getFloat(value) + ) { + return null; + } + } + + return { + 'border-image-outset': { + important, + value + } + }; + } + + /** + * Returns border repeat. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBorderImageRepeat( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const lowerValue = value.toLowerCase(); + + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue)) { + return { + 'border-image-repeat': { + important, + value: lowerValue + } + }; + } + + const parts = lowerValue.split(/ +/); + + if (parts.length > 2) { + return null; + } + + for (const part of parts) { + if (!BORDER_IMAGE_REPEAT.includes(part)) { + return null; + } + } + + return { + 'border-image-repeat': { + important, + value + } + }; + } + /** * Returns border width. * diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts index e1efdb0f6..cb3b5b8d5 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts @@ -7,6 +7,8 @@ const DEGREE_REGEXP = /^[0-9]+deg$/; const URL_REGEXP = /^url\(\s*([^)]*)\s*\)$/; const INTEGER_REGEXP = /^[0-9]+$/; const FLOAT_REGEXP = /^[0-9.]+$/; +const GRADIENT_REGEXP = + /^(repeating-linear|linear|radial|repeating-radial|conic|repeating-conic)-gradient\([^)]+\)$/; const GLOBALS = ['inherit', 'initial', 'unset', 'revert']; const COLORS = [ 'silver', @@ -255,6 +257,19 @@ export default class CSSStyleDeclarationValueParser { return null; } + /** + * Returns gradient. + * + * @param value Value. + * @returns Parsed value. + */ + public static getGradient(value: string): string { + if (GRADIENT_REGEXP.test(value)) { + return value; + } + return null; + } + /** * Returns color. *