From 8c77dde1caeef474b06fbf4a6ec61e1b6981cc0c Mon Sep 17 00:00:00 2001 From: kalvens Date: Mon, 4 Jul 2022 15:42:47 -0500 Subject: [PATCH 01/84] #513@patch: Selector item not factoring in selectors after a psuedo selector. --- packages/happy-dom/src/query-selector/SelectorItem.ts | 5 +++-- .../happy-dom/test/query-selector/QuerySelector.test.ts | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/happy-dom/src/query-selector/SelectorItem.ts b/packages/happy-dom/src/query-selector/SelectorItem.ts index 8016fefed..a5aa0ead4 100644 --- a/packages/happy-dom/src/query-selector/SelectorItem.ts +++ b/packages/happy-dom/src/query-selector/SelectorItem.ts @@ -28,12 +28,13 @@ export default class SelectorItem { * @param selector Selector. */ constructor(selector: string) { - const [baseSelector, psuedoSelector] = selector.split(':'); + const baseSelector = selector.replace(new RegExp(PSUEDO_REGEXP, 'g'), ''); this.isAll = baseSelector === '*'; this.isID = !this.isAll ? selector.startsWith('#') : false; this.isAttribute = !this.isAll && !this.isID && baseSelector.includes('['); - this.isPseudo = !this.isAll && !this.isID && psuedoSelector !== undefined; + // If baseSelector !== selector then some psuedo selector was replaced above + this.isPseudo = !this.isAll && !this.isID && baseSelector !== selector; this.isClass = !this.isAll && !this.isID && new RegExp(CLASS_REGEXP, 'g').test(baseSelector); this.tagName = !this.isAll && !this.isID ? baseSelector.match(TAG_NAME_REGEXP) : null; this.tagName = this.tagName ? this.tagName[0].toUpperCase() : null; diff --git a/packages/happy-dom/test/query-selector/QuerySelector.test.ts b/packages/happy-dom/test/query-selector/QuerySelector.test.ts index 84e99f1cf..f95192bff 100644 --- a/packages/happy-dom/test/query-selector/QuerySelector.test.ts +++ b/packages/happy-dom/test/query-selector/QuerySelector.test.ts @@ -625,5 +625,14 @@ describe('QuerySelector', () => { expect(div.querySelector('#id')).toEqual(div2); }); + + it('Does not find input with selector of input:not([list])[type="search"]', () => { + const div = document.createElement('div'); + const input = document.createElement('input'); + input.setAttribute('type', 'text'); + div.appendChild(input); + + expect(div.querySelector('input:not([list])[type="search"]')).toBeNull(); + }); }); }); From 2f337f69d7c49cea9f3b9f1159b97ea1100c9045 Mon Sep 17 00:00:00 2001 From: kalvens Date: Mon, 4 Jul 2022 17:06:45 -0500 Subject: [PATCH 02/84] #501@patch: allow proxy keys besides strings --- packages/happy-dom/src/nodes/html-element/DatasetUtility.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/happy-dom/src/nodes/html-element/DatasetUtility.ts b/packages/happy-dom/src/nodes/html-element/DatasetUtility.ts index f4305197e..e3d2ad017 100644 --- a/packages/happy-dom/src/nodes/html-element/DatasetUtility.ts +++ b/packages/happy-dom/src/nodes/html-element/DatasetUtility.ts @@ -23,6 +23,8 @@ export default class DatasetUtility { * @returns Kebab cased string. */ public static camelCaseToKebab(text: string): string { - return text.replace(/[A-Z]+(?![a-z])|[A-Z]/g, ($, ofs) => (ofs ? '-' : '') + $.toLowerCase()); + return text + .toString() + .replace(/[A-Z]+(?![a-z])|[A-Z]/g, ($, ofs) => (ofs ? '-' : '') + $.toLowerCase()); } } From 2801008755018ea777dc9297a31dde9201ff92b7 Mon Sep 17 00:00:00 2001 From: Shukhrat Mukimov Date: Fri, 8 Jul 2022 12:15:31 +0500 Subject: [PATCH 03/84] #467@patch: Fix getRootNode() undefined on document. --- packages/happy-dom/src/nodes/document/Document.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/happy-dom/src/nodes/document/Document.ts b/packages/happy-dom/src/nodes/document/Document.ts index 43235f9ac..2b20a0127 100644 --- a/packages/happy-dom/src/nodes/document/Document.ts +++ b/packages/happy-dom/src/nodes/document/Document.ts @@ -233,7 +233,7 @@ export default class Document extends Node implements IDocument { let activeElement: IHTMLElement = this._activeElement; while (rootNode !== this) { activeElement = (rootNode).host; - rootNode = activeElement.getRootNode(); + rootNode = activeElement ? activeElement.getRootNode() : this; } return activeElement; } From 9dd1363117151c24fa37045d4fb2d18fc99b8668 Mon Sep 17 00:00:00 2001 From: Benjamin Koltes Date: Sun, 10 Jul 2022 17:11:07 +0000 Subject: [PATCH 04/84] #0@trivial: Fix typo in contribute.md. --- docs/contributing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributing.md b/docs/contributing.md index bac1abe05..ecb5db164 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -73,6 +73,6 @@ The release process in Happy DOM is completely automated. In order to determine | ------- | ------------------------------------------------------------ | | trivial | Use this version type if the change doesn't affect the end user. The change will not be displayed in the release notes. | | patch | Bug fixes should use this version type. | -| minor | New features that doesn' break anything for the end user should have this version type. | +| minor | New features that doesn't break anything for the end user should have this version type. | | major | Braking changes should use this version type. | From dfb53374ef9102b64f47434070dd74eb72447ddb Mon Sep 17 00:00:00 2001 From: Vincent Lemeunier Date: Sun, 17 Jul 2022 19:07:03 +0200 Subject: [PATCH 05/84] #542@patch: Fix Response constructor issue. --- packages/happy-dom/src/fetch/Response.ts | 7 ++++-- .../happy-dom/test/fetch/Response.test.ts | 22 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 packages/happy-dom/test/fetch/Response.test.ts diff --git a/packages/happy-dom/src/fetch/Response.ts b/packages/happy-dom/src/fetch/Response.ts index 7ca263144..a368b4665 100644 --- a/packages/happy-dom/src/fetch/Response.ts +++ b/packages/happy-dom/src/fetch/Response.ts @@ -13,9 +13,12 @@ export default class Response extends NodeFetch.Response implements IResponse { /** * Constructor. + * + * @param [body] An object defining a body for the response (can be omitted) + * @param [init] An options object containing any custom settings that you want to apply to the response, or an empty object (which is the default value) */ - constructor() { - super(); + constructor(body?: NodeFetch.BodyInit, init?: NodeFetch.ResponseInit) { + super(body, init); this._ownerDocument = (this.constructor)._ownerDocument; } diff --git a/packages/happy-dom/test/fetch/Response.test.ts b/packages/happy-dom/test/fetch/Response.test.ts new file mode 100644 index 000000000..008629896 --- /dev/null +++ b/packages/happy-dom/test/fetch/Response.test.ts @@ -0,0 +1,22 @@ +import Response from '../../src/fetch/Response'; +import Window from '../../src/window/Window'; + +jest.unmock('node-fetch'); + +beforeAll(() => { + const window = new Window(); + Response._ownerDocument = window.document; +}); + +afterAll(() => { + Response._ownerDocument = null; +}); + +describe('Response', () => { + it('Forwards constructor arguments to base implementation.', async () => { + const response = new Response('hello there', { status: 404 }); + + expect(response.status).toBe(404); + expect(await response.text()).toBe('hello there'); + }); +}); From 11ef7acd3eeb228569a500f4c69adcf356320bf0 Mon Sep 17 00:00:00 2001 From: a-wing <1@233.email> Date: Thu, 14 Jul 2022 02:52:31 +0800 Subject: [PATCH 06/84] #541@minor: Adds support for Blob.arrayBuffer(). https://developer.mozilla.org/en-US/docs/Web/API/Blob/arrayBuffer --- packages/happy-dom/src/file/Blob.ts | 14 ++++++++++++++ packages/happy-dom/test/file/Blob.test.ts | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/packages/happy-dom/src/file/Blob.ts b/packages/happy-dom/src/file/Blob.ts index 7d48b800c..bfcfe8842 100644 --- a/packages/happy-dom/src/file/Blob.ts +++ b/packages/happy-dom/src/file/Blob.ts @@ -110,6 +110,20 @@ export default class Blob implements IBlob { return blob; } + + /** + * Returns a Promise that resolves to a ArrayBuffer. + * + * @returns ArrayBuffer. + */ + + // Reference: + // https://github.com/web-std/io/blob/c88170bf24f064adfbb3586a21fb76650ca5a9ab/packages/blob/src/blob.js#L139-L148 + // https://stackoverflow.com/questions/8609289/convert-a-binary-nodejs-buffer-to-javascript-arraybuffer + public async arrayBuffer(): Promise { + return new Uint8Array(this._buffer).buffer + } + /** * Returns a Promise that resolves to a text. * diff --git a/packages/happy-dom/test/file/Blob.test.ts b/packages/happy-dom/test/file/Blob.test.ts index 50f300ce8..de5d205b6 100644 --- a/packages/happy-dom/test/file/Blob.test.ts +++ b/packages/happy-dom/test/file/Blob.test.ts @@ -22,6 +22,20 @@ describe('Blob', () => { }); }); + // Reference: + // https://github.com/web-std/io/blob/c88170bf24f064adfbb3586a21fb76650ca5a9ab/packages/blob/test/blob.spec.js#L35-L44 + describe('arrayBuffer()', () => { + it('Returns "Promise".', async () => { + const str = 'TEST'; + const blob = new Blob([str]); + const buffer = await blob.arrayBuffer(); + const result = new Uint8Array(buffer); + for (let i = 0; i < result.length; ++i) { + expect(result[i]).toBe(str[i].charCodeAt(0)); + } + }); + }); + describe('toString()', () => { it('Returns "[object Blob]".', () => { const blob = new Blob(['TEST']); From 3e8b8556fb4e2eace7b7354767466a58506ed46a Mon Sep 17 00:00:00 2001 From: David Ortner Date: Fri, 22 Jul 2022 15:28:00 +0200 Subject: [PATCH 07/84] #344@trivial: Starts on fixing computed styles. --- packages/happy-dom/src/css/CSSParser.ts | 2 +- .../happy-dom/src/css/CSSStyleDeclaration.ts | 5026 ----------------- .../src/css/computed-style/ComputedStyle.ts | 54 + .../ComputedStylePropertyParser.ts | 163 + .../CSSStyleDeclarationDefaultValues.ts | 295 + .../CSSStyleDeclarationNodeDefaultValues.ts | 1095 ++++ .../AbstractCSSStyleDeclaration.ts | 236 + .../css/declaration/CSSStyleDeclaration.ts | 4444 +++++++++++++++ .../declaration/CSSStyleDeclarationUtility.ts | 54 + .../src/css/rules/CSSFontFaceRule.ts | 2 +- .../src/css/rules/CSSKeyframeRule.ts | 2 +- .../src/css/rules/CSSKeyframesRule.ts | 2 +- .../happy-dom/src/css/rules/CSSStyleRule.ts | 2 +- packages/happy-dom/src/event/Event.ts | 5 +- .../happy-dom/src/exception/DOMException.ts | 6 +- .../src/exception/DOMExceptionNameEnum.ts | 3 +- packages/happy-dom/src/index.ts | 2 +- .../src/{attribute => nodes/attr}/Attr.ts | 19 +- packages/happy-dom/src/nodes/attr/IAttr.ts | 15 + .../src/nodes/child-node/ChildNodeUtility.ts | 3 +- .../happy-dom/src/nodes/document/Document.ts | 18 +- .../happy-dom/src/nodes/document/IDocument.ts | 6 +- .../happy-dom/src/nodes/element/Element.ts | 23 +- .../happy-dom/src/nodes/element/IElement.ts | 16 +- .../src/nodes/html-element/HTMLElement.ts | 86 +- .../src/nodes/html-element/IHTMLElement.ts | 4 +- .../html-link-element/HTMLLinkElement.ts | 4 +- .../html-script-element/HTMLScriptElement.ts | 4 +- .../html-style-element/HTMLStyleElement.ts | 2 +- packages/happy-dom/src/nodes/node/INode.ts | 2 + packages/happy-dom/src/nodes/node/Node.ts | 8 +- .../happy-dom/src/nodes/node/NodeTypeEnum.ts | 2 + .../src/nodes/svg-element/ISVGElement.ts | 2 +- .../src/nodes/svg-element/SVGElement.ts | 10 +- packages/happy-dom/src/window/IWindow.ts | 4 +- packages/happy-dom/src/window/Window.ts | 10 +- .../test/css/CSSStyleDeclaration.test.ts | 91 - .../declaration/CSSStyleDeclaration.test.ts | 270 + .../data/CSSStyleDeclarationCamelCaseKeys.ts} | 0 .../data/CSSStyleDeclarationKebabCaseKeys.ts | 371 ++ .../test/nodes/document/Document.test.ts | 2 +- .../test/nodes/element/Element.test.ts | 95 +- .../nodes/html-element/HTMLElement.test.ts | 24 +- packages/happy-dom/test/window/Window.test.ts | 2 +- 44 files changed, 7245 insertions(+), 5241 deletions(-) delete mode 100644 packages/happy-dom/src/css/CSSStyleDeclaration.ts create mode 100644 packages/happy-dom/src/css/computed-style/ComputedStyle.ts create mode 100644 packages/happy-dom/src/css/computed-style/ComputedStylePropertyParser.ts create mode 100644 packages/happy-dom/src/css/computed-style/config/CSSStyleDeclarationDefaultValues.ts create mode 100644 packages/happy-dom/src/css/computed-style/config/CSSStyleDeclarationNodeDefaultValues.ts create mode 100644 packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts create mode 100644 packages/happy-dom/src/css/declaration/CSSStyleDeclaration.ts create mode 100644 packages/happy-dom/src/css/declaration/CSSStyleDeclarationUtility.ts rename packages/happy-dom/src/{attribute => nodes/attr}/Attr.ts (73%) create mode 100644 packages/happy-dom/src/nodes/attr/IAttr.ts delete mode 100644 packages/happy-dom/test/css/CSSStyleDeclaration.test.ts create mode 100644 packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts rename packages/happy-dom/test/css/{data/CSSStyleDeclarationStyleProperties.ts => declaration/data/CSSStyleDeclarationCamelCaseKeys.ts} (100%) create mode 100644 packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationKebabCaseKeys.ts diff --git a/packages/happy-dom/src/css/CSSParser.ts b/packages/happy-dom/src/css/CSSParser.ts index fabebd64b..caa0e5568 100644 --- a/packages/happy-dom/src/css/CSSParser.ts +++ b/packages/happy-dom/src/css/CSSParser.ts @@ -4,7 +4,7 @@ import CSSStyleRule from './rules/CSSStyleRule'; import CSSKeyframeRule from './rules/CSSKeyframeRule'; import CSSKeyframesRule from './rules/CSSKeyframesRule'; import CSSMediaRule from './rules/CSSMediaRule'; -import CSSStyleDeclaration from './CSSStyleDeclaration'; +import CSSStyleDeclaration from './declaration/CSSStyleDeclaration'; const COMMENT_REGEXP = /\/\*[^*]*\*\//gm; diff --git a/packages/happy-dom/src/css/CSSStyleDeclaration.ts b/packages/happy-dom/src/css/CSSStyleDeclaration.ts deleted file mode 100644 index 5a932aa96..000000000 --- a/packages/happy-dom/src/css/CSSStyleDeclaration.ts +++ /dev/null @@ -1,5026 +0,0 @@ -import Attr from '../attribute/Attr'; -import CSSRule from './CSSRule'; - -/** - * CSS Style Declaration. - */ -export default class CSSStyleDeclaration { - public readonly length = 0; - public readonly parentRule: CSSRule = null; - private _attributes: { [k: string]: Attr } = null; - private _computedStyleElement: { isConnected: boolean } = null; - - /** - * Constructor. - * - * @param [attributes] Attributes. - * @param [computedStyleElement] Computed style element. - * @param computedStyleElement.isConnected - */ - constructor( - attributes: { [k: string]: Attr } = {}, - computedStyleElement: { isConnected: boolean } = null - ) { - const style = attributes['style']; - let index = 0; - - this._attributes = attributes; - this._computedStyleElement = computedStyleElement; - - if (style && style.value) { - const parts = style.value.split(';'); - for (const part of parts) { - if (part) { - const [name] = part.trim().split(':'); - this[index] = name; - index++; - } - } - } - - (this.length) = index; - } - - /** - * - */ - public get alignContent(): string { - return this.getPropertyValue('align-content'); - } - /** - * - */ - public set alignContent(alignContent: string) { - this.setProperty('align-content', alignContent); - } - - /** - * - */ - public get alignItems(): string { - return this.getPropertyValue('align-items'); - } - /** - * - */ - public set alignItems(alignItems: string) { - this.setProperty('align-items', alignItems); - } - - /** - * - */ - public get alignSelf(): string { - return this.getPropertyValue('align-self'); - } - /** - * - */ - public set alignSelf(alignSelf: string) { - this.setProperty('align-self', alignSelf); - } - - /** - * - */ - public get alignmentBaseline(): string { - return this.getPropertyValue('alignment-baseline'); - } - /** - * - */ - public set alignmentBaseline(alignmentBaseline: string) { - this.setProperty('alignment-baseline', alignmentBaseline); - } - - /** - * - */ - public get all(): string { - return this.getPropertyValue('all'); - } - /** - * - */ - public set all(all: string) { - this.setProperty('all', all); - } - - /** - * - */ - public get animation(): string { - return this.getPropertyValue('animation'); - } - /** - * - */ - public set animation(animation: string) { - this.setProperty('animation', animation); - } - - /** - * - */ - public get animationDelay(): string { - return this.getPropertyValue('animation-delay'); - } - /** - * - */ - public set animationDelay(animationDelay: string) { - this.setProperty('animation-delay', animationDelay); - } - - /** - * - */ - public get animationDirection(): string { - return this.getPropertyValue('animation-direction'); - } - /** - * - */ - public set animationDirection(animationDirection: string) { - this.setProperty('animation-direction', animationDirection); - } - - /** - * - */ - public get animationDuration(): string { - return this.getPropertyValue('animation-duration'); - } - /** - * - */ - public set animationDuration(animationDuration: string) { - this.setProperty('animation-duration', animationDuration); - } - - /** - * - */ - public get animationFillMode(): string { - return this.getPropertyValue('animation-fill-mode'); - } - /** - * - */ - public set animationFillMode(animationFillMode: string) { - this.setProperty('animation-fill-mode', animationFillMode); - } - - /** - * - */ - public get animationIterationCount(): string { - return this.getPropertyValue('animation-iteration-count'); - } - /** - * - */ - public set animationIterationCount(animationIterationCount: string) { - this.setProperty('animation-iteration-count', animationIterationCount); - } - - /** - * - */ - public get animationName(): string { - return this.getPropertyValue('animation-name'); - } - /** - * - */ - public set animationName(animationName: string) { - this.setProperty('animation-name', animationName); - } - - /** - * - */ - public get animationPlayState(): string { - return this.getPropertyValue('animation-play-state'); - } - /** - * - */ - public set animationPlayState(animationPlayState: string) { - this.setProperty('animation-play-state', animationPlayState); - } - - /** - * - */ - public get animationTimingFunction(): string { - return this.getPropertyValue('animation-timing-function'); - } - /** - * - */ - public set animationTimingFunction(animationTimingFunction: string) { - this.setProperty('animation-timing-function', animationTimingFunction); - } - - /** - * - */ - public get appearance(): string { - return this.getPropertyValue('appearance'); - } - /** - * - */ - public set appearance(appearance: string) { - this.setProperty('appearance', appearance); - } - - /** - * - */ - public get backdropFilter(): string { - return this.getPropertyValue('backdrop-filter'); - } - /** - * - */ - public set backdropFilter(backdropFilter: string) { - this.setProperty('backdrop-filter', backdropFilter); - } - - /** - * - */ - public get backfaceVisibility(): string { - return this.getPropertyValue('backface-visibility'); - } - /** - * - */ - public set backfaceVisibility(backfaceVisibility: string) { - this.setProperty('backface-visibility', backfaceVisibility); - } - - /** - * - */ - public get background(): string { - return this.getPropertyValue('background'); - } - /** - * - */ - public set background(background: string) { - this.setProperty('background', background); - } - - /** - * - */ - public get backgroundAttachment(): string { - return this.getPropertyValue('background-attachment'); - } - /** - * - */ - public set backgroundAttachment(backgroundAttachment: string) { - this.setProperty('background-attachment', backgroundAttachment); - } - - /** - * - */ - public get backgroundBlendMode(): string { - return this.getPropertyValue('background-blend-mode'); - } - /** - * - */ - public set backgroundBlendMode(backgroundBlendMode: string) { - this.setProperty('background-blend-mode', backgroundBlendMode); - } - - /** - * - */ - public get backgroundClip(): string { - return this.getPropertyValue('background-clip'); - } - /** - * - */ - public set backgroundClip(backgroundClip: string) { - this.setProperty('background-clip', backgroundClip); - } - - /** - * - */ - public get backgroundColor(): string { - return this.getPropertyValue('background-color'); - } - /** - * - */ - public set backgroundColor(backgroundColor: string) { - this.setProperty('background-color', backgroundColor); - } - - /** - * - */ - public get backgroundImage(): string { - return this.getPropertyValue('background-image'); - } - /** - * - */ - public set backgroundImage(backgroundImage: string) { - this.setProperty('background-image', backgroundImage); - } - - /** - * - */ - public get backgroundOrigin(): string { - return this.getPropertyValue('background-origin'); - } - /** - * - */ - public set backgroundOrigin(backgroundOrigin: string) { - this.setProperty('background-origin', backgroundOrigin); - } - - /** - * - */ - public get backgroundPosition(): string { - return this.getPropertyValue('background-position'); - } - /** - * - */ - public set backgroundPosition(backgroundPosition: string) { - this.setProperty('background-position', backgroundPosition); - } - - /** - * - */ - public get backgroundPositionX(): string { - return this.getPropertyValue('background-position-x'); - } - /** - * - */ - public set backgroundPositionX(backgroundPositionX: string) { - this.setProperty('background-position-x', backgroundPositionX); - } - - /** - * - */ - public get backgroundPositionY(): string { - return this.getPropertyValue('background-position-y'); - } - /** - * - */ - public set backgroundPositionY(backgroundPositionY: string) { - this.setProperty('background-position-y', backgroundPositionY); - } - - /** - * - */ - public get backgroundRepeat(): string { - return this.getPropertyValue('background-repeat'); - } - /** - * - */ - public set backgroundRepeat(backgroundRepeat: string) { - this.setProperty('background-repeat', backgroundRepeat); - } - - /** - * - */ - public get backgroundRepeatX(): string { - return this.getPropertyValue('background-repeat-x'); - } - /** - * - */ - public set backgroundRepeatX(backgroundRepeatX: string) { - this.setProperty('background-repeat-x', backgroundRepeatX); - } - - /** - * - */ - public get backgroundRepeatY(): string { - return this.getPropertyValue('background-repeat-y'); - } - /** - * - */ - public set backgroundRepeatY(backgroundRepeatY: string) { - this.setProperty('background-repeat-y', backgroundRepeatY); - } - - /** - * - */ - public get backgroundSize(): string { - return this.getPropertyValue('background-size'); - } - /** - * - */ - public set backgroundSize(backgroundSize: string) { - this.setProperty('background-size', backgroundSize); - } - - /** - * - */ - public get baselineShift(): string { - return this.getPropertyValue('baseline-shift'); - } - /** - * - */ - public set baselineShift(baselineShift: string) { - this.setProperty('baseline-shift', baselineShift); - } - - /** - * - */ - public get blockSize(): string { - return this.getPropertyValue('block-size'); - } - /** - * - */ - public set blockSize(blockSize: string) { - this.setProperty('block-size', blockSize); - } - - /** - * - */ - public get border(): string { - return this.getPropertyValue('border'); - } - /** - * - */ - public set border(border: string) { - this.setProperty('border', border); - } - - /** - * - */ - public get borderBlockEnd(): string { - return this.getPropertyValue('border-block-end'); - } - /** - * - */ - public set borderBlockEnd(borderBlockEnd: string) { - this.setProperty('border-block-end', borderBlockEnd); - } - - /** - * - */ - public get borderBlockEndColor(): string { - return this.getPropertyValue('border-block-end-color'); - } - /** - * - */ - public set borderBlockEndColor(borderBlockEndColor: string) { - this.setProperty('border-block-end-color', borderBlockEndColor); - } - - /** - * - */ - public get borderBlockEndStyle(): string { - return this.getPropertyValue('border-block-end-style'); - } - /** - * - */ - public set borderBlockEndStyle(borderBlockEndStyle: string) { - this.setProperty('border-block-end-style', borderBlockEndStyle); - } - - /** - * - */ - public get borderBlockEndWidth(): string { - return this.getPropertyValue('border-block-end-width'); - } - /** - * - */ - public set borderBlockEndWidth(borderBlockEndWidth: string) { - this.setProperty('border-block-end-width', borderBlockEndWidth); - } - - /** - * - */ - public get borderBlockStart(): string { - return this.getPropertyValue('border-block-start'); - } - /** - * - */ - public set borderBlockStart(borderBlockStart: string) { - this.setProperty('border-block-start', borderBlockStart); - } - - /** - * - */ - public get borderBlockStartColor(): string { - return this.getPropertyValue('border-block-start-color'); - } - /** - * - */ - public set borderBlockStartColor(borderBlockStartColor: string) { - this.setProperty('border-block-start-color', borderBlockStartColor); - } - - /** - * - */ - public get borderBlockStartStyle(): string { - return this.getPropertyValue('border-block-start-style'); - } - /** - * - */ - public set borderBlockStartStyle(borderBlockStartStyle: string) { - this.setProperty('border-block-start-style', borderBlockStartStyle); - } - - /** - * - */ - public get borderBlockStartWidth(): string { - return this.getPropertyValue('border-block-start-width'); - } - /** - * - */ - public set borderBlockStartWidth(borderBlockStartWidth: string) { - this.setProperty('border-block-start-width', borderBlockStartWidth); - } - - /** - * - */ - public get borderBottom(): string { - return this.getPropertyValue('border-bottom'); - } - /** - * - */ - public set borderBottom(borderBottom: string) { - this.setProperty('border-bottom', borderBottom); - } - - /** - * - */ - public get borderBottomColor(): string { - return this.getPropertyValue('border-bottom-color'); - } - /** - * - */ - public set borderBottomColor(borderBottomColor: string) { - this.setProperty('border-bottom-color', borderBottomColor); - } - - /** - * - */ - public get borderBottomLeftRadius(): string { - return this.getPropertyValue('border-bottom-left-radius'); - } - /** - * - */ - public set borderBottomLeftRadius(borderBottomLeftRadius: string) { - this.setProperty('border-bottom-left-radius', borderBottomLeftRadius); - } - - /** - * - */ - public get borderBottomRightRadius(): string { - return this.getPropertyValue('border-bottom-right-radius'); - } - /** - * - */ - public set borderBottomRightRadius(borderBottomRightRadius: string) { - this.setProperty('border-bottom-right-radius', borderBottomRightRadius); - } - - /** - * - */ - public get borderBottomStyle(): string { - return this.getPropertyValue('border-bottom-style'); - } - /** - * - */ - public set borderBottomStyle(borderBottomStyle: string) { - this.setProperty('border-bottom-style', borderBottomStyle); - } - - /** - * - */ - public get borderBottomWidth(): string { - return this.getPropertyValue('border-bottom-width'); - } - /** - * - */ - public set borderBottomWidth(borderBottomWidth: string) { - this.setProperty('border-bottom-width', borderBottomWidth); - } - - /** - * - */ - public get borderCollapse(): string { - return this.getPropertyValue('border-collapse'); - } - /** - * - */ - public set borderCollapse(borderCollapse: string) { - this.setProperty('border-collapse', borderCollapse); - } - - /** - * - */ - public get borderColor(): string { - return this.getPropertyValue('border-color'); - } - /** - * - */ - public set borderColor(borderColor: string) { - this.setProperty('border-color', borderColor); - } - - /** - * - */ - public get borderImage(): string { - return this.getPropertyValue('border-image'); - } - /** - * - */ - public set borderImage(borderImage: string) { - this.setProperty('border-image', borderImage); - } - - /** - * - */ - public get borderImageOutset(): string { - return this.getPropertyValue('border-image-outset'); - } - /** - * - */ - public set borderImageOutset(borderImageOutset: string) { - this.setProperty('border-image-outset', borderImageOutset); - } - - /** - * - */ - public get borderImageRepeat(): string { - return this.getPropertyValue('border-image-repeat'); - } - /** - * - */ - public set borderImageRepeat(borderImageRepeat: string) { - this.setProperty('border-image-repeat', borderImageRepeat); - } - - /** - * - */ - public get borderImageSlice(): string { - return this.getPropertyValue('border-image-slice'); - } - /** - * - */ - public set borderImageSlice(borderImageSlice: string) { - this.setProperty('border-image-slice', borderImageSlice); - } - - /** - * - */ - public get borderImageSource(): string { - return this.getPropertyValue('border-image-source'); - } - /** - * - */ - public set borderImageSource(borderImageSource: string) { - this.setProperty('border-image-source', borderImageSource); - } - - /** - * - */ - public get borderImageWidth(): string { - return this.getPropertyValue('border-image-width'); - } - /** - * - */ - public set borderImageWidth(borderImageWidth: string) { - this.setProperty('border-image-width', borderImageWidth); - } - - /** - * - */ - public get borderInlineEnd(): string { - return this.getPropertyValue('border-inline-end'); - } - /** - * - */ - public set borderInlineEnd(borderInlineEnd: string) { - this.setProperty('border-inline-end', borderInlineEnd); - } - - /** - * - */ - public get borderInlineEndColor(): string { - return this.getPropertyValue('border-inline-end-color'); - } - /** - * - */ - public set borderInlineEndColor(borderInlineEndColor: string) { - this.setProperty('border-inline-end-color', borderInlineEndColor); - } - - /** - * - */ - public get borderInlineEndStyle(): string { - return this.getPropertyValue('border-inline-end-style'); - } - /** - * - */ - public set borderInlineEndStyle(borderInlineEndStyle: string) { - this.setProperty('border-inline-end-style', borderInlineEndStyle); - } - - /** - * - */ - public get borderInlineEndWidth(): string { - return this.getPropertyValue('border-inline-end-width'); - } - /** - * - */ - public set borderInlineEndWidth(borderInlineEndWidth: string) { - this.setProperty('border-inline-end-width', borderInlineEndWidth); - } - - /** - * - */ - public get borderInlineStart(): string { - return this.getPropertyValue('border-inline-start'); - } - /** - * - */ - public set borderInlineStart(borderInlineStart: string) { - this.setProperty('border-inline-start', borderInlineStart); - } - - /** - * - */ - public get borderInlineStartColor(): string { - return this.getPropertyValue('border-inline-start-color'); - } - /** - * - */ - public set borderInlineStartColor(borderInlineStartColor: string) { - this.setProperty('border-inline-start-color', borderInlineStartColor); - } - - /** - * - */ - public get borderInlineStartStyle(): string { - return this.getPropertyValue('border-inline-start-style'); - } - /** - * - */ - public set borderInlineStartStyle(borderInlineStartStyle: string) { - this.setProperty('border-inline-start-style', borderInlineStartStyle); - } - - /** - * - */ - public get borderInlineStartWidth(): string { - return this.getPropertyValue('border-inline-start-width'); - } - /** - * - */ - public set borderInlineStartWidth(borderInlineStartWidth: string) { - this.setProperty('border-inline-start-width', borderInlineStartWidth); - } - - /** - * - */ - public get borderLeft(): string { - return this.getPropertyValue('border-left'); - } - /** - * - */ - public set borderLeft(borderLeft: string) { - this.setProperty('border-left', borderLeft); - } - - /** - * - */ - public get borderLeftColor(): string { - return this.getPropertyValue('border-left-color'); - } - /** - * - */ - public set borderLeftColor(borderLeftColor: string) { - this.setProperty('border-left-color', borderLeftColor); - } - - /** - * - */ - public get borderLeftStyle(): string { - return this.getPropertyValue('border-left-style'); - } - /** - * - */ - public set borderLeftStyle(borderLeftStyle: string) { - this.setProperty('border-left-style', borderLeftStyle); - } - - /** - * - */ - public get borderLeftWidth(): string { - return this.getPropertyValue('border-left-width'); - } - /** - * - */ - public set borderLeftWidth(borderLeftWidth: string) { - this.setProperty('border-left-width', borderLeftWidth); - } - - /** - * - */ - public get borderRadius(): string { - return this.getPropertyValue('border-radius'); - } - /** - * - */ - public set borderRadius(borderRadius: string) { - this.setProperty('border-radius', borderRadius); - } - - /** - * - */ - public get borderRight(): string { - return this.getPropertyValue('border-right'); - } - /** - * - */ - public set borderRight(borderRight: string) { - this.setProperty('border-right', borderRight); - } - - /** - * - */ - public get borderRightColor(): string { - return this.getPropertyValue('border-right-color'); - } - /** - * - */ - public set borderRightColor(borderRightColor: string) { - this.setProperty('border-right-color', borderRightColor); - } - - /** - * - */ - public get borderRightStyle(): string { - return this.getPropertyValue('border-right-style'); - } - /** - * - */ - public set borderRightStyle(borderRightStyle: string) { - this.setProperty('border-right-style', borderRightStyle); - } - - /** - * - */ - public get borderRightWidth(): string { - return this.getPropertyValue('border-right-width'); - } - /** - * - */ - public set borderRightWidth(borderRightWidth: string) { - this.setProperty('border-right-width', borderRightWidth); - } - - /** - * - */ - public get borderSpacing(): string { - return this.getPropertyValue('border-spacing'); - } - /** - * - */ - public set borderSpacing(borderSpacing: string) { - this.setProperty('border-spacing', borderSpacing); - } - - /** - * - */ - public get borderStyle(): string { - return this.getPropertyValue('border-style'); - } - /** - * - */ - public set borderStyle(borderStyle: string) { - this.setProperty('border-style', borderStyle); - } - - /** - * - */ - public get borderTop(): string { - return this.getPropertyValue('border-top'); - } - /** - * - */ - public set borderTop(borderTop: string) { - this.setProperty('border-top', borderTop); - } - - /** - * - */ - public get borderTopColor(): string { - return this.getPropertyValue('border-top-color'); - } - /** - * - */ - public set borderTopColor(borderTopColor: string) { - this.setProperty('border-top-color', borderTopColor); - } - - /** - * - */ - public get borderTopLeftRadius(): string { - return this.getPropertyValue('border-top-left-radius'); - } - /** - * - */ - public set borderTopLeftRadius(borderTopLeftRadius: string) { - this.setProperty('border-top-left-radius', borderTopLeftRadius); - } - - /** - * - */ - public get borderTopRightRadius(): string { - return this.getPropertyValue('border-top-right-radius'); - } - /** - * - */ - public set borderTopRightRadius(borderTopRightRadius: string) { - this.setProperty('border-top-right-radius', borderTopRightRadius); - } - - /** - * - */ - public get borderTopStyle(): string { - return this.getPropertyValue('border-top-style'); - } - /** - * - */ - public set borderTopStyle(borderTopStyle: string) { - this.setProperty('border-top-style', borderTopStyle); - } - - /** - * - */ - public get borderTopWidth(): string { - return this.getPropertyValue('border-top-width'); - } - /** - * - */ - public set borderTopWidth(borderTopWidth: string) { - this.setProperty('border-top-width', borderTopWidth); - } - - /** - * - */ - public get borderWidth(): string { - return this.getPropertyValue('border-width'); - } - /** - * - */ - public set borderWidth(borderWidth: string) { - this.setProperty('border-width', borderWidth); - } - - /** - * - */ - public get bottom(): string { - return this.getPropertyValue('bottom'); - } - /** - * - */ - public set bottom(bottom: string) { - this.setProperty('bottom', bottom); - } - - /** - * - */ - public get boxShadow(): string { - return this.getPropertyValue('box-shadow'); - } - /** - * - */ - public set boxShadow(boxShadow: string) { - this.setProperty('box-shadow', boxShadow); - } - - /** - * - */ - public get boxSizing(): string { - return this.getPropertyValue('box-sizing'); - } - /** - * - */ - public set boxSizing(boxSizing: string) { - this.setProperty('box-sizing', boxSizing); - } - - /** - * - */ - public get breakAfter(): string { - return this.getPropertyValue('break-after'); - } - /** - * - */ - public set breakAfter(breakAfter: string) { - this.setProperty('break-after', breakAfter); - } - - /** - * - */ - public get breakBefore(): string { - return this.getPropertyValue('break-before'); - } - /** - * - */ - public set breakBefore(breakBefore: string) { - this.setProperty('break-before', breakBefore); - } - - /** - * - */ - public get breakInside(): string { - return this.getPropertyValue('break-inside'); - } - /** - * - */ - public set breakInside(breakInside: string) { - this.setProperty('break-inside', breakInside); - } - - /** - * - */ - public get bufferedRendering(): string { - return this.getPropertyValue('buffered-rendering'); - } - /** - * - */ - public set bufferedRendering(bufferedRendering: string) { - this.setProperty('buffered-rendering', bufferedRendering); - } - - /** - * - */ - public get captionSide(): string { - return this.getPropertyValue('caption-side'); - } - /** - * - */ - public set captionSide(captionSide: string) { - this.setProperty('caption-side', captionSide); - } - - /** - * - */ - public get caretColor(): string { - return this.getPropertyValue('caret-color'); - } - /** - * - */ - public set caretColor(caretColor: string) { - this.setProperty('caret-color', caretColor); - } - - /** - * - */ - public get clear(): string { - return this.getPropertyValue('clear'); - } - /** - * - */ - public set clear(clear: string) { - this.setProperty('clear', clear); - } - - /** - * - */ - public get clip(): string { - return this.getPropertyValue('clip'); - } - /** - * - */ - public set clip(clip: string) { - this.setProperty('clip', clip); - } - - /** - * - */ - public get clipPath(): string { - return this.getPropertyValue('clip-path'); - } - /** - * - */ - public set clipPath(clipPath: string) { - this.setProperty('clip-path', clipPath); - } - - /** - * - */ - public get clipRule(): string { - return this.getPropertyValue('clip-rule'); - } - /** - * - */ - public set clipRule(clipRule: string) { - this.setProperty('clip-rule', clipRule); - } - - /** - * - */ - public get color(): string { - return this.getPropertyValue('color'); - } - /** - * - */ - public set color(color: string) { - this.setProperty('color', color); - } - - /** - * - */ - public get colorInterpolation(): string { - return this.getPropertyValue('color-interpolation'); - } - /** - * - */ - public set colorInterpolation(colorInterpolation: string) { - this.setProperty('color-interpolation', colorInterpolation); - } - - /** - * - */ - public get colorInterpolationFilters(): string { - return this.getPropertyValue('color-interpolation-filters'); - } - /** - * - */ - public set colorInterpolationFilters(colorInterpolationFilters: string) { - this.setProperty('color-interpolation-filters', colorInterpolationFilters); - } - - /** - * - */ - public get colorRendering(): string { - return this.getPropertyValue('color-rendering'); - } - /** - * - */ - public set colorRendering(colorRendering: string) { - this.setProperty('color-rendering', colorRendering); - } - - /** - * - */ - public get colorScheme(): string { - return this.getPropertyValue('color-scheme'); - } - /** - * - */ - public set colorScheme(colorScheme: string) { - this.setProperty('color-scheme', colorScheme); - } - - /** - * - */ - public get columnCount(): string { - return this.getPropertyValue('column-count'); - } - /** - * - */ - public set columnCount(columnCount: string) { - this.setProperty('column-count', columnCount); - } - - /** - * - */ - public get columnFill(): string { - return this.getPropertyValue('column-fill'); - } - /** - * - */ - public set columnFill(columnFill: string) { - this.setProperty('column-fill', columnFill); - } - - /** - * - */ - public get columnGap(): string { - return this.getPropertyValue('column-gap'); - } - /** - * - */ - public set columnGap(columnGap: string) { - this.setProperty('column-gap', columnGap); - } - - /** - * - */ - public get columnRule(): string { - return this.getPropertyValue('column-rule'); - } - /** - * - */ - public set columnRule(columnRule: string) { - this.setProperty('column-rule', columnRule); - } - - /** - * - */ - public get columnRuleColor(): string { - return this.getPropertyValue('column-rule-color'); - } - /** - * - */ - public set columnRuleColor(columnRuleColor: string) { - this.setProperty('column-rule-color', columnRuleColor); - } - - /** - * - */ - public get columnRuleStyle(): string { - return this.getPropertyValue('column-rule-style'); - } - /** - * - */ - public set columnRuleStyle(columnRuleStyle: string) { - this.setProperty('column-rule-style', columnRuleStyle); - } - - /** - * - */ - public get columnRuleWidth(): string { - return this.getPropertyValue('column-rule-width'); - } - /** - * - */ - public set columnRuleWidth(columnRuleWidth: string) { - this.setProperty('column-rule-width', columnRuleWidth); - } - - /** - * - */ - public get columnSpan(): string { - return this.getPropertyValue('column-span'); - } - /** - * - */ - public set columnSpan(columnSpan: string) { - this.setProperty('column-span', columnSpan); - } - - /** - * - */ - public get columnWidth(): string { - return this.getPropertyValue('column-width'); - } - /** - * - */ - public set columnWidth(columnWidth: string) { - this.setProperty('column-width', columnWidth); - } - - /** - * - */ - public get columns(): string { - return this.getPropertyValue('columns'); - } - /** - * - */ - public set columns(columns: string) { - this.setProperty('columns', columns); - } - - /** - * - */ - public get contain(): string { - return this.getPropertyValue('contain'); - } - /** - * - */ - public set contain(contain: string) { - this.setProperty('contain', contain); - } - - /** - * - */ - public get containIntrinsicSize(): string { - return this.getPropertyValue('contain-intrinsic-size'); - } - /** - * - */ - public set containIntrinsicSize(containIntrinsicSize: string) { - this.setProperty('contain-intrinsic-size', containIntrinsicSize); - } - - /** - * - */ - public get content(): string { - return this.getPropertyValue('content'); - } - /** - * - */ - public set content(content: string) { - this.setProperty('content', content); - } - - /** - * - */ - public get contentVisibility(): string { - return this.getPropertyValue('content-visibility'); - } - /** - * - */ - public set contentVisibility(contentVisibility: string) { - this.setProperty('content-visibility', contentVisibility); - } - - /** - * - */ - public get counterIncrement(): string { - return this.getPropertyValue('counter-increment'); - } - /** - * - */ - public set counterIncrement(counterIncrement: string) { - this.setProperty('counter-increment', counterIncrement); - } - - /** - * - */ - public get counterReset(): string { - return this.getPropertyValue('counter-reset'); - } - /** - * - */ - public set counterReset(counterReset: string) { - this.setProperty('counter-reset', counterReset); - } - - /** - * - */ - public get counterSet(): string { - return this.getPropertyValue('counter-set'); - } - /** - * - */ - public set counterSet(counterSet: string) { - this.setProperty('counter-set', counterSet); - } - - /** - * - */ - public get cssFloat(): string { - return this.getPropertyValue('css-float'); - } - /** - * - */ - public set cssFloat(cssFloat: string) { - this.setProperty('css-float', cssFloat); - } - - /** - * - */ - public get cursor(): string { - return this.getPropertyValue('cursor'); - } - /** - * - */ - public set cursor(cursor: string) { - this.setProperty('cursor', cursor); - } - - /** - * - */ - public get cx(): string { - return this.getPropertyValue('cx'); - } - /** - * - */ - public set cx(cx: string) { - this.setProperty('cx', cx); - } - - /** - * - */ - public get cy(): string { - return this.getPropertyValue('cy'); - } - /** - * - */ - public set cy(cy: string) { - this.setProperty('cy', cy); - } - - /** - * - */ - public get d(): string { - return this.getPropertyValue('d'); - } - /** - * - */ - public set d(d: string) { - this.setProperty('d', d); - } - - /** - * - */ - public get direction(): string { - return this.getPropertyValue('direction'); - } - /** - * - */ - public set direction(direction: string) { - this.setProperty('direction', direction); - } - - /** - * - */ - public get display(): string { - return this.getPropertyValue('display'); - } - /** - * - */ - public set display(display: string) { - this.setProperty('display', display); - } - - /** - * - */ - public get dominantBaseline(): string { - return this.getPropertyValue('dominant-baseline'); - } - /** - * - */ - public set dominantBaseline(dominantBaseline: string) { - this.setProperty('dominant-baseline', dominantBaseline); - } - - /** - * - */ - public get emptyCells(): string { - return this.getPropertyValue('empty-cells'); - } - /** - * - */ - public set emptyCells(emptyCells: string) { - this.setProperty('empty-cells', emptyCells); - } - - /** - * - */ - public get fill(): string { - return this.getPropertyValue('fill'); - } - /** - * - */ - public set fill(fill: string) { - this.setProperty('fill', fill); - } - - /** - * - */ - public get fillOpacity(): string { - return this.getPropertyValue('fill-opacity'); - } - /** - * - */ - public set fillOpacity(fillOpacity: string) { - this.setProperty('fill-opacity', fillOpacity); - } - - /** - * - */ - public get fillRule(): string { - return this.getPropertyValue('fill-rule'); - } - /** - * - */ - public set fillRule(fillRule: string) { - this.setProperty('fill-rule', fillRule); - } - - /** - * - */ - public get filter(): string { - return this.getPropertyValue('filter'); - } - /** - * - */ - public set filter(filter: string) { - this.setProperty('filter', filter); - } - - /** - * - */ - public get flex(): string { - return this.getPropertyValue('flex'); - } - /** - * - */ - public set flex(flex: string) { - this.setProperty('flex', flex); - } - - /** - * - */ - public get flexBasis(): string { - return this.getPropertyValue('flex-basis'); - } - /** - * - */ - public set flexBasis(flexBasis: string) { - this.setProperty('flex-basis', flexBasis); - } - - /** - * - */ - public get flexDirection(): string { - return this.getPropertyValue('flex-direction'); - } - /** - * - */ - public set flexDirection(flexDirection: string) { - this.setProperty('flex-direction', flexDirection); - } - - /** - * - */ - public get flexFlow(): string { - return this.getPropertyValue('flex-flow'); - } - /** - * - */ - public set flexFlow(flexFlow: string) { - this.setProperty('flex-flow', flexFlow); - } - - /** - * - */ - public get flexGrow(): string { - return this.getPropertyValue('flex-grow'); - } - /** - * - */ - public set flexGrow(flexGrow: string) { - this.setProperty('flex-grow', flexGrow); - } - - /** - * - */ - public get flexShrink(): string { - return this.getPropertyValue('flex-shrink'); - } - /** - * - */ - public set flexShrink(flexShrink: string) { - this.setProperty('flex-shrink', flexShrink); - } - - /** - * - */ - public get flexWrap(): string { - return this.getPropertyValue('flex-wrap'); - } - /** - * - */ - public set flexWrap(flexWrap: string) { - this.setProperty('flex-wrap', flexWrap); - } - - /** - * - */ - public get float(): string { - return this.getPropertyValue('float'); - } - /** - * - */ - public set float(float: string) { - this.setProperty('float', float); - } - - /** - * - */ - public get floodColor(): string { - return this.getPropertyValue('flood-color'); - } - /** - * - */ - public set floodColor(floodColor: string) { - this.setProperty('flood-color', floodColor); - } - - /** - * - */ - public get floodOpacity(): string { - return this.getPropertyValue('flood-opacity'); - } - /** - * - */ - public set floodOpacity(floodOpacity: string) { - this.setProperty('flood-opacity', floodOpacity); - } - - /** - * - */ - public get font(): string { - return this.getPropertyValue('font'); - } - /** - * - */ - public set font(font: string) { - this.setProperty('font', font); - } - - /** - * - */ - public get fontDisplay(): string { - return this.getPropertyValue('font-display'); - } - /** - * - */ - public set fontDisplay(fontDisplay: string) { - this.setProperty('font-display', fontDisplay); - } - - /** - * - */ - public get fontFamily(): string { - return this.getPropertyValue('font-family'); - } - /** - * - */ - public set fontFamily(fontFamily: string) { - this.setProperty('font-family', fontFamily); - } - - /** - * - */ - public get fontFeatureSettings(): string { - return this.getPropertyValue('font-feature-settings'); - } - /** - * - */ - public set fontFeatureSettings(fontFeatureSettings: string) { - this.setProperty('font-feature-settings', fontFeatureSettings); - } - - /** - * - */ - public get fontKerning(): string { - return this.getPropertyValue('font-kerning'); - } - /** - * - */ - public set fontKerning(fontKerning: string) { - this.setProperty('font-kerning', fontKerning); - } - - /** - * - */ - public get fontOpticalSizing(): string { - return this.getPropertyValue('font-optical-sizing'); - } - /** - * - */ - public set fontOpticalSizing(fontOpticalSizing: string) { - this.setProperty('font-optical-sizing', fontOpticalSizing); - } - - /** - * - */ - public get fontSize(): string { - return this.getPropertyValue('font-size'); - } - /** - * - */ - public set fontSize(fontSize: string) { - this.setProperty('font-size', fontSize); - } - - /** - * - */ - public get fontStretch(): string { - return this.getPropertyValue('font-stretch'); - } - /** - * - */ - public set fontStretch(fontStretch: string) { - this.setProperty('font-stretch', fontStretch); - } - - /** - * - */ - public get fontStyle(): string { - return this.getPropertyValue('font-style'); - } - /** - * - */ - public set fontStyle(fontStyle: string) { - this.setProperty('font-style', fontStyle); - } - - /** - * - */ - public get fontVariant(): string { - return this.getPropertyValue('font-variant'); - } - /** - * - */ - public set fontVariant(fontVariant: string) { - this.setProperty('font-variant', fontVariant); - } - - /** - * - */ - public get fontVariantCaps(): string { - return this.getPropertyValue('font-variant-caps'); - } - /** - * - */ - public set fontVariantCaps(fontVariantCaps: string) { - this.setProperty('font-variant-caps', fontVariantCaps); - } - - /** - * - */ - public get fontVariantEastAsian(): string { - return this.getPropertyValue('font-variant-east-asian'); - } - /** - * - */ - public set fontVariantEastAsian(fontVariantEastAsian: string) { - this.setProperty('font-variant-east-asian', fontVariantEastAsian); - } - - /** - * - */ - public get fontVariantLigatures(): string { - return this.getPropertyValue('font-variant-ligatures'); - } - /** - * - */ - public set fontVariantLigatures(fontVariantLigatures: string) { - this.setProperty('font-variant-ligatures', fontVariantLigatures); - } - - /** - * - */ - public get fontVariantNumeric(): string { - return this.getPropertyValue('font-variant-numeric'); - } - /** - * - */ - public set fontVariantNumeric(fontVariantNumeric: string) { - this.setProperty('font-variant-numeric', fontVariantNumeric); - } - - /** - * - */ - public get fontVariationSettings(): string { - return this.getPropertyValue('font-variation-settings'); - } - /** - * - */ - public set fontVariationSettings(fontVariationSettings: string) { - this.setProperty('font-variation-settings', fontVariationSettings); - } - - /** - * - */ - public get fontWeight(): string { - return this.getPropertyValue('font-weight'); - } - /** - * - */ - public set fontWeight(fontWeight: string) { - this.setProperty('font-weight', fontWeight); - } - - /** - * - */ - public get gap(): string { - return this.getPropertyValue('gap'); - } - /** - * - */ - public set gap(gap: string) { - this.setProperty('gap', gap); - } - - /** - * - */ - public get grid(): string { - return this.getPropertyValue('grid'); - } - /** - * - */ - public set grid(grid: string) { - this.setProperty('grid', grid); - } - - /** - * - */ - public get gridArea(): string { - return this.getPropertyValue('grid-area'); - } - /** - * - */ - public set gridArea(gridArea: string) { - this.setProperty('grid-area', gridArea); - } - - /** - * - */ - public get gridAutoColumns(): string { - return this.getPropertyValue('grid-auto-columns'); - } - /** - * - */ - public set gridAutoColumns(gridAutoColumns: string) { - this.setProperty('grid-auto-columns', gridAutoColumns); - } - - /** - * - */ - public get gridAutoFlow(): string { - return this.getPropertyValue('grid-auto-flow'); - } - /** - * - */ - public set gridAutoFlow(gridAutoFlow: string) { - this.setProperty('grid-auto-flow', gridAutoFlow); - } - - /** - * - */ - public get gridAutoRows(): string { - return this.getPropertyValue('grid-auto-rows'); - } - /** - * - */ - public set gridAutoRows(gridAutoRows: string) { - this.setProperty('grid-auto-rows', gridAutoRows); - } - - /** - * - */ - public get gridColumn(): string { - return this.getPropertyValue('grid-column'); - } - /** - * - */ - public set gridColumn(gridColumn: string) { - this.setProperty('grid-column', gridColumn); - } - - /** - * - */ - public get gridColumnEnd(): string { - return this.getPropertyValue('grid-column-end'); - } - /** - * - */ - public set gridColumnEnd(gridColumnEnd: string) { - this.setProperty('grid-column-end', gridColumnEnd); - } - - /** - * - */ - public get gridColumnGap(): string { - return this.getPropertyValue('grid-column-gap'); - } - /** - * - */ - public set gridColumnGap(gridColumnGap: string) { - this.setProperty('grid-column-gap', gridColumnGap); - } - - /** - * - */ - public get gridColumnStart(): string { - return this.getPropertyValue('grid-column-start'); - } - /** - * - */ - public set gridColumnStart(gridColumnStart: string) { - this.setProperty('grid-column-start', gridColumnStart); - } - - /** - * - */ - public get gridGap(): string { - return this.getPropertyValue('grid-gap'); - } - /** - * - */ - public set gridGap(gridGap: string) { - this.setProperty('grid-gap', gridGap); - } - - /** - * - */ - public get gridRow(): string { - return this.getPropertyValue('grid-row'); - } - /** - * - */ - public set gridRow(gridRow: string) { - this.setProperty('grid-row', gridRow); - } - - /** - * - */ - public get gridRowEnd(): string { - return this.getPropertyValue('grid-row-end'); - } - /** - * - */ - public set gridRowEnd(gridRowEnd: string) { - this.setProperty('grid-row-end', gridRowEnd); - } - - /** - * - */ - public get gridRowGap(): string { - return this.getPropertyValue('grid-row-gap'); - } - /** - * - */ - public set gridRowGap(gridRowGap: string) { - this.setProperty('grid-row-gap', gridRowGap); - } - - /** - * - */ - public get gridRowStart(): string { - return this.getPropertyValue('grid-row-start'); - } - /** - * - */ - public set gridRowStart(gridRowStart: string) { - this.setProperty('grid-row-start', gridRowStart); - } - - /** - * - */ - public get gridTemplate(): string { - return this.getPropertyValue('grid-template'); - } - /** - * - */ - public set gridTemplate(gridTemplate: string) { - this.setProperty('grid-template', gridTemplate); - } - - /** - * - */ - public get gridTemplateAreas(): string { - return this.getPropertyValue('grid-template-areas'); - } - /** - * - */ - public set gridTemplateAreas(gridTemplateAreas: string) { - this.setProperty('grid-template-areas', gridTemplateAreas); - } - - /** - * - */ - public get gridTemplateColumns(): string { - return this.getPropertyValue('grid-template-columns'); - } - /** - * - */ - public set gridTemplateColumns(gridTemplateColumns: string) { - this.setProperty('grid-template-columns', gridTemplateColumns); - } - - /** - * - */ - public get gridTemplateRows(): string { - return this.getPropertyValue('grid-template-rows'); - } - /** - * - */ - public set gridTemplateRows(gridTemplateRows: string) { - this.setProperty('grid-template-rows', gridTemplateRows); - } - - /** - * - */ - public get height(): string { - return this.getPropertyValue('height'); - } - /** - * - */ - public set height(height: string) { - this.setProperty('height', height); - } - - /** - * - */ - public get hyphens(): string { - return this.getPropertyValue('hyphens'); - } - /** - * - */ - public set hyphens(hyphens: string) { - this.setProperty('hyphens', hyphens); - } - - /** - * - */ - public get imageOrientation(): string { - return this.getPropertyValue('image-orientation'); - } - /** - * - */ - public set imageOrientation(imageOrientation: string) { - this.setProperty('image-orientation', imageOrientation); - } - - /** - * - */ - public get imageRendering(): string { - return this.getPropertyValue('image-rendering'); - } - /** - * - */ - public set imageRendering(imageRendering: string) { - this.setProperty('image-rendering', imageRendering); - } - - /** - * - */ - public get inherits(): string { - return this.getPropertyValue('inherits'); - } - /** - * - */ - public set inherits(inherits: string) { - this.setProperty('inherits', inherits); - } - - /** - * - */ - public get initialValue(): string { - return this.getPropertyValue('initial-value'); - } - /** - * - */ - public set initialValue(initialValue: string) { - this.setProperty('initial-value', initialValue); - } - - /** - * - */ - public get inlineSize(): string { - return this.getPropertyValue('inline-size'); - } - /** - * - */ - public set inlineSize(inlineSize: string) { - this.setProperty('inline-size', inlineSize); - } - - /** - * - */ - public get isolation(): string { - return this.getPropertyValue('isolation'); - } - /** - * - */ - public set isolation(isolation: string) { - this.setProperty('isolation', isolation); - } - - /** - * - */ - public get justifyContent(): string { - return this.getPropertyValue('justify-content'); - } - /** - * - */ - public set justifyContent(justifyContent: string) { - this.setProperty('justify-content', justifyContent); - } - - /** - * - */ - public get justifyItems(): string { - return this.getPropertyValue('justify-items'); - } - /** - * - */ - public set justifyItems(justifyItems: string) { - this.setProperty('justify-items', justifyItems); - } - - /** - * - */ - public get justifySelf(): string { - return this.getPropertyValue('justify-self'); - } - /** - * - */ - public set justifySelf(justifySelf: string) { - this.setProperty('justify-self', justifySelf); - } - - /** - * - */ - public get left(): string { - return this.getPropertyValue('left'); - } - /** - * - */ - public set left(left: string) { - this.setProperty('left', left); - } - - /** - * - */ - public get letterSpacing(): string { - return this.getPropertyValue('letter-spacing'); - } - /** - * - */ - public set letterSpacing(letterSpacing: string) { - this.setProperty('letter-spacing', letterSpacing); - } - - /** - * - */ - public get lightingColor(): string { - return this.getPropertyValue('lighting-color'); - } - /** - * - */ - public set lightingColor(lightingColor: string) { - this.setProperty('lighting-color', lightingColor); - } - - /** - * - */ - public get lineBreak(): string { - return this.getPropertyValue('line-break'); - } - /** - * - */ - public set lineBreak(lineBreak: string) { - this.setProperty('line-break', lineBreak); - } - - /** - * - */ - public get lineHeight(): string { - return this.getPropertyValue('line-height'); - } - /** - * - */ - public set lineHeight(lineHeight: string) { - this.setProperty('line-height', lineHeight); - } - - /** - * - */ - public get listStyle(): string { - return this.getPropertyValue('list-style'); - } - /** - * - */ - public set listStyle(listStyle: string) { - this.setProperty('list-style', listStyle); - } - - /** - * - */ - public get listStyleImage(): string { - return this.getPropertyValue('list-style-image'); - } - /** - * - */ - public set listStyleImage(listStyleImage: string) { - this.setProperty('list-style-image', listStyleImage); - } - - /** - * - */ - public get listStylePosition(): string { - return this.getPropertyValue('list-style-position'); - } - /** - * - */ - public set listStylePosition(listStylePosition: string) { - this.setProperty('list-style-position', listStylePosition); - } - - /** - * - */ - public get listStyleType(): string { - return this.getPropertyValue('list-style-type'); - } - /** - * - */ - public set listStyleType(listStyleType: string) { - this.setProperty('list-style-type', listStyleType); - } - - /** - * - */ - public get margin(): string { - return this.getPropertyValue('margin'); - } - /** - * - */ - public set margin(margin: string) { - this.setProperty('margin', margin); - } - - /** - * - */ - public get marginBlockEnd(): string { - return this.getPropertyValue('margin-block-end'); - } - /** - * - */ - public set marginBlockEnd(marginBlockEnd: string) { - this.setProperty('margin-block-end', marginBlockEnd); - } - - /** - * - */ - public get marginBlockStart(): string { - return this.getPropertyValue('margin-block-start'); - } - /** - * - */ - public set marginBlockStart(marginBlockStart: string) { - this.setProperty('margin-block-start', marginBlockStart); - } - - /** - * - */ - public get marginBottom(): string { - return this.getPropertyValue('margin-bottom'); - } - /** - * - */ - public set marginBottom(marginBottom: string) { - this.setProperty('margin-bottom', marginBottom); - } - - /** - * - */ - public get marginInlineEnd(): string { - return this.getPropertyValue('margin-inline-end'); - } - /** - * - */ - public set marginInlineEnd(marginInlineEnd: string) { - this.setProperty('margin-inline-end', marginInlineEnd); - } - - /** - * - */ - public get marginInlineStart(): string { - return this.getPropertyValue('margin-inline-start'); - } - /** - * - */ - public set marginInlineStart(marginInlineStart: string) { - this.setProperty('margin-inline-start', marginInlineStart); - } - - /** - * - */ - public get marginLeft(): string { - return this.getPropertyValue('margin-left'); - } - /** - * - */ - public set marginLeft(marginLeft: string) { - this.setProperty('margin-left', marginLeft); - } - - /** - * - */ - public get marginRight(): string { - return this.getPropertyValue('margin-right'); - } - /** - * - */ - public set marginRight(marginRight: string) { - this.setProperty('margin-right', marginRight); - } - - /** - * - */ - public get marginTop(): string { - return this.getPropertyValue('margin-top'); - } - /** - * - */ - public set marginTop(marginTop: string) { - this.setProperty('margin-top', marginTop); - } - - /** - * - */ - public get marker(): string { - return this.getPropertyValue('marker'); - } - /** - * - */ - public set marker(marker: string) { - this.setProperty('marker', marker); - } - - /** - * - */ - public get markerEnd(): string { - return this.getPropertyValue('marker-end'); - } - /** - * - */ - public set markerEnd(markerEnd: string) { - this.setProperty('marker-end', markerEnd); - } - - /** - * - */ - public get markerMid(): string { - return this.getPropertyValue('marker-mid'); - } - /** - * - */ - public set markerMid(markerMid: string) { - this.setProperty('marker-mid', markerMid); - } - - /** - * - */ - public get markerStart(): string { - return this.getPropertyValue('marker-start'); - } - /** - * - */ - public set markerStart(markerStart: string) { - this.setProperty('marker-start', markerStart); - } - - /** - * - */ - public get mask(): string { - return this.getPropertyValue('mask'); - } - /** - * - */ - public set mask(mask: string) { - this.setProperty('mask', mask); - } - - /** - * - */ - public get maskType(): string { - return this.getPropertyValue('mask-type'); - } - /** - * - */ - public set maskType(maskType: string) { - this.setProperty('mask-type', maskType); - } - - /** - * - */ - public get maxBlockSize(): string { - return this.getPropertyValue('max-block-size'); - } - /** - * - */ - public set maxBlockSize(maxBlockSize: string) { - this.setProperty('max-block-size', maxBlockSize); - } - - /** - * - */ - public get maxHeight(): string { - return this.getPropertyValue('max-height'); - } - /** - * - */ - public set maxHeight(maxHeight: string) { - this.setProperty('max-height', maxHeight); - } - - /** - * - */ - public get maxInlineSize(): string { - return this.getPropertyValue('max-inline-size'); - } - /** - * - */ - public set maxInlineSize(maxInlineSize: string) { - this.setProperty('max-inline-size', maxInlineSize); - } - - /** - * - */ - public get maxWidth(): string { - return this.getPropertyValue('max-width'); - } - /** - * - */ - public set maxWidth(maxWidth: string) { - this.setProperty('max-width', maxWidth); - } - - /** - * - */ - public get maxZoom(): string { - return this.getPropertyValue('max-zoom'); - } - /** - * - */ - public set maxZoom(maxZoom: string) { - this.setProperty('max-zoom', maxZoom); - } - - /** - * - */ - public get minBlockSize(): string { - return this.getPropertyValue('min-block-size'); - } - /** - * - */ - public set minBlockSize(minBlockSize: string) { - this.setProperty('min-block-size', minBlockSize); - } - - /** - * - */ - public get minHeight(): string { - return this.getPropertyValue('min-height'); - } - /** - * - */ - public set minHeight(minHeight: string) { - this.setProperty('min-height', minHeight); - } - - /** - * - */ - public get minInlineSize(): string { - return this.getPropertyValue('min-inline-size'); - } - /** - * - */ - public set minInlineSize(minInlineSize: string) { - this.setProperty('min-inline-size', minInlineSize); - } - - /** - * - */ - public get minWidth(): string { - return this.getPropertyValue('min-width'); - } - /** - * - */ - public set minWidth(minWidth: string) { - this.setProperty('min-width', minWidth); - } - - /** - * - */ - public get minZoom(): string { - return this.getPropertyValue('min-zoom'); - } - /** - * - */ - public set minZoom(minZoom: string) { - this.setProperty('min-zoom', minZoom); - } - - /** - * - */ - public get mixBlendMode(): string { - return this.getPropertyValue('mix-blend-mode'); - } - /** - * - */ - public set mixBlendMode(mixBlendMode: string) { - this.setProperty('mix-blend-mode', mixBlendMode); - } - - /** - * - */ - public get objectFit(): string { - return this.getPropertyValue('object-fit'); - } - /** - * - */ - public set objectFit(objectFit: string) { - this.setProperty('object-fit', objectFit); - } - - /** - * - */ - public get objectPosition(): string { - return this.getPropertyValue('object-position'); - } - /** - * - */ - public set objectPosition(objectPosition: string) { - this.setProperty('object-position', objectPosition); - } - - /** - * - */ - public get offset(): string { - return this.getPropertyValue('offset'); - } - /** - * - */ - public set offset(offset: string) { - this.setProperty('offset', offset); - } - - /** - * - */ - public get offsetDistance(): string { - return this.getPropertyValue('offset-distance'); - } - /** - * - */ - public set offsetDistance(offsetDistance: string) { - this.setProperty('offset-distance', offsetDistance); - } - - /** - * - */ - public get offsetPath(): string { - return this.getPropertyValue('offset-path'); - } - /** - * - */ - public set offsetPath(offsetPath: string) { - this.setProperty('offset-path', offsetPath); - } - - /** - * - */ - public get offsetRotate(): string { - return this.getPropertyValue('offset-rotate'); - } - /** - * - */ - public set offsetRotate(offsetRotate: string) { - this.setProperty('offset-rotate', offsetRotate); - } - - /** - * - */ - public get opacity(): string { - return this.getPropertyValue('opacity'); - } - /** - * - */ - public set opacity(opacity: string) { - this.setProperty('opacity', opacity); - } - - /** - * - */ - public get order(): string { - return this.getPropertyValue('order'); - } - /** - * - */ - public set order(order: string) { - this.setProperty('order', order); - } - - /** - * - */ - public get orientation(): string { - return this.getPropertyValue('orientation'); - } - /** - * - */ - public set orientation(orientation: string) { - this.setProperty('orientation', orientation); - } - - /** - * - */ - public get orphans(): string { - return this.getPropertyValue('orphans'); - } - /** - * - */ - public set orphans(orphans: string) { - this.setProperty('orphans', orphans); - } - - /** - * - */ - public get outline(): string { - return this.getPropertyValue('outline'); - } - /** - * - */ - public set outline(outline: string) { - this.setProperty('outline', outline); - } - - /** - * - */ - public get outlineColor(): string { - return this.getPropertyValue('outline-color'); - } - /** - * - */ - public set outlineColor(outlineColor: string) { - this.setProperty('outline-color', outlineColor); - } - - /** - * - */ - public get outlineOffset(): string { - return this.getPropertyValue('outline-offset'); - } - /** - * - */ - public set outlineOffset(outlineOffset: string) { - this.setProperty('outline-offset', outlineOffset); - } - - /** - * - */ - public get outlineStyle(): string { - return this.getPropertyValue('outline-style'); - } - /** - * - */ - public set outlineStyle(outlineStyle: string) { - this.setProperty('outline-style', outlineStyle); - } - - /** - * - */ - public get outlineWidth(): string { - return this.getPropertyValue('outline-width'); - } - /** - * - */ - public set outlineWidth(outlineWidth: string) { - this.setProperty('outline-width', outlineWidth); - } - - /** - * - */ - public get overflow(): string { - return this.getPropertyValue('overflow'); - } - /** - * - */ - public set overflow(overflow: string) { - this.setProperty('overflow', overflow); - } - - /** - * - */ - public get overflowAnchor(): string { - return this.getPropertyValue('overflow-anchor'); - } - /** - * - */ - public set overflowAnchor(overflowAnchor: string) { - this.setProperty('overflow-anchor', overflowAnchor); - } - - /** - * - */ - public get overflowWrap(): string { - return this.getPropertyValue('overflow-wrap'); - } - /** - * - */ - public set overflowWrap(overflowWrap: string) { - this.setProperty('overflow-wrap', overflowWrap); - } - - /** - * - */ - public get overflowX(): string { - return this.getPropertyValue('overflow-x'); - } - /** - * - */ - public set overflowX(overflowX: string) { - this.setProperty('overflow-x', overflowX); - } - - /** - * - */ - public get overflowY(): string { - return this.getPropertyValue('overflow-y'); - } - /** - * - */ - public set overflowY(overflowY: string) { - this.setProperty('overflow-y', overflowY); - } - - /** - * - */ - public get overscrollBehavior(): string { - return this.getPropertyValue('overscroll-behavior'); - } - /** - * - */ - public set overscrollBehavior(overscrollBehavior: string) { - this.setProperty('overscroll-behavior', overscrollBehavior); - } - - /** - * - */ - public get overscrollBehaviorBlock(): string { - return this.getPropertyValue('overscroll-behavior-block'); - } - /** - * - */ - public set overscrollBehaviorBlock(overscrollBehaviorBlock: string) { - this.setProperty('overscroll-behavior-block', overscrollBehaviorBlock); - } - - /** - * - */ - public get overscrollBehaviorInline(): string { - return this.getPropertyValue('overscroll-behavior-inline'); - } - /** - * - */ - public set overscrollBehaviorInline(overscrollBehaviorInline: string) { - this.setProperty('overscroll-behavior-inline', overscrollBehaviorInline); - } - - /** - * - */ - public get overscrollBehaviorX(): string { - return this.getPropertyValue('overscroll-behavior-x'); - } - /** - * - */ - public set overscrollBehaviorX(overscrollBehaviorX: string) { - this.setProperty('overscroll-behavior-x', overscrollBehaviorX); - } - - /** - * - */ - public get overscrollBehaviorY(): string { - return this.getPropertyValue('overscroll-behavior-y'); - } - /** - * - */ - public set overscrollBehaviorY(overscrollBehaviorY: string) { - this.setProperty('overscroll-behavior-y', overscrollBehaviorY); - } - - /** - * - */ - public get padding(): string { - return this.getPropertyValue('padding'); - } - /** - * - */ - public set padding(padding: string) { - this.setProperty('padding', padding); - } - - /** - * - */ - public get paddingBlockEnd(): string { - return this.getPropertyValue('padding-block-end'); - } - /** - * - */ - public set paddingBlockEnd(paddingBlockEnd: string) { - this.setProperty('padding-block-end', paddingBlockEnd); - } - - /** - * - */ - public get paddingBlockStart(): string { - return this.getPropertyValue('padding-block-start'); - } - /** - * - */ - public set paddingBlockStart(paddingBlockStart: string) { - this.setProperty('padding-block-start', paddingBlockStart); - } - - /** - * - */ - public get paddingBottom(): string { - return this.getPropertyValue('padding-bottom'); - } - /** - * - */ - public set paddingBottom(paddingBottom: string) { - this.setProperty('padding-bottom', paddingBottom); - } - - /** - * - */ - public get paddingInlineEnd(): string { - return this.getPropertyValue('padding-inline-end'); - } - /** - * - */ - public set paddingInlineEnd(paddingInlineEnd: string) { - this.setProperty('padding-inline-end', paddingInlineEnd); - } - - /** - * - */ - public get paddingInlineStart(): string { - return this.getPropertyValue('padding-inline-start'); - } - /** - * - */ - public set paddingInlineStart(paddingInlineStart: string) { - this.setProperty('padding-inline-start', paddingInlineStart); - } - - /** - * - */ - public get paddingLeft(): string { - return this.getPropertyValue('padding-left'); - } - /** - * - */ - public set paddingLeft(paddingLeft: string) { - this.setProperty('padding-left', paddingLeft); - } - - /** - * - */ - public get paddingRight(): string { - return this.getPropertyValue('padding-right'); - } - /** - * - */ - public set paddingRight(paddingRight: string) { - this.setProperty('padding-right', paddingRight); - } - - /** - * - */ - public get paddingTop(): string { - return this.getPropertyValue('padding-top'); - } - /** - * - */ - public set paddingTop(paddingTop: string) { - this.setProperty('padding-top', paddingTop); - } - - /** - * - */ - public get page(): string { - return this.getPropertyValue('page'); - } - /** - * - */ - public set page(page: string) { - this.setProperty('page', page); - } - - /** - * - */ - public get pageBreakAfter(): string { - return this.getPropertyValue('page-break-after'); - } - /** - * - */ - public set pageBreakAfter(pageBreakAfter: string) { - this.setProperty('page-break-after', pageBreakAfter); - } - - /** - * - */ - public get pageBreakBefore(): string { - return this.getPropertyValue('page-break-before'); - } - /** - * - */ - public set pageBreakBefore(pageBreakBefore: string) { - this.setProperty('page-break-before', pageBreakBefore); - } - - /** - * - */ - public get pageBreakInside(): string { - return this.getPropertyValue('page-break-inside'); - } - /** - * - */ - public set pageBreakInside(pageBreakInside: string) { - this.setProperty('page-break-inside', pageBreakInside); - } - - /** - * - */ - public get pageOrientation(): string { - return this.getPropertyValue('page-orientation'); - } - /** - * - */ - public set pageOrientation(pageOrientation: string) { - this.setProperty('page-orientation', pageOrientation); - } - - /** - * - */ - public get paintOrder(): string { - return this.getPropertyValue('paint-order'); - } - /** - * - */ - public set paintOrder(paintOrder: string) { - this.setProperty('paint-order', paintOrder); - } - - /** - * - */ - public get perspective(): string { - return this.getPropertyValue('perspective'); - } - /** - * - */ - public set perspective(perspective: string) { - this.setProperty('perspective', perspective); - } - - /** - * - */ - public get perspectiveOrigin(): string { - return this.getPropertyValue('perspective-origin'); - } - /** - * - */ - public set perspectiveOrigin(perspectiveOrigin: string) { - this.setProperty('perspective-origin', perspectiveOrigin); - } - - /** - * - */ - public get placeContent(): string { - return this.getPropertyValue('place-content'); - } - /** - * - */ - public set placeContent(placeContent: string) { - this.setProperty('place-content', placeContent); - } - - /** - * - */ - public get placeItems(): string { - return this.getPropertyValue('place-items'); - } - /** - * - */ - public set placeItems(placeItems: string) { - this.setProperty('place-items', placeItems); - } - - /** - * - */ - public get placeSelf(): string { - return this.getPropertyValue('place-self'); - } - /** - * - */ - public set placeSelf(placeSelf: string) { - this.setProperty('place-self', placeSelf); - } - - /** - * - */ - public get pointerEvents(): string { - return this.getPropertyValue('pointer-events'); - } - /** - * - */ - public set pointerEvents(pointerEvents: string) { - this.setProperty('pointer-events', pointerEvents); - } - - /** - * - */ - public get position(): string { - return this.getPropertyValue('position'); - } - /** - * - */ - public set position(position: string) { - this.setProperty('position', position); - } - - /** - * - */ - public get quotes(): string { - return this.getPropertyValue('quotes'); - } - /** - * - */ - public set quotes(quotes: string) { - this.setProperty('quotes', quotes); - } - - /** - * - */ - public get r(): string { - return this.getPropertyValue('r'); - } - /** - * - */ - public set r(r: string) { - this.setProperty('r', r); - } - - /** - * - */ - public get resize(): string { - return this.getPropertyValue('resize'); - } - /** - * - */ - public set resize(resize: string) { - this.setProperty('resize', resize); - } - - /** - * - */ - public get right(): string { - return this.getPropertyValue('right'); - } - /** - * - */ - public set right(right: string) { - this.setProperty('right', right); - } - - /** - * - */ - public get rowGap(): string { - return this.getPropertyValue('row-gap'); - } - /** - * - */ - public set rowGap(rowGap: string) { - this.setProperty('row-gap', rowGap); - } - - /** - * - */ - public get rubyPosition(): string { - return this.getPropertyValue('ruby-position'); - } - /** - * - */ - public set rubyPosition(rubyPosition: string) { - this.setProperty('ruby-position', rubyPosition); - } - - /** - * - */ - public get rx(): string { - return this.getPropertyValue('rx'); - } - /** - * - */ - public set rx(rx: string) { - this.setProperty('rx', rx); - } - - /** - * - */ - public get ry(): string { - return this.getPropertyValue('ry'); - } - /** - * - */ - public set ry(ry: string) { - this.setProperty('ry', ry); - } - - /** - * - */ - public get scrollBehavior(): string { - return this.getPropertyValue('scroll-behavior'); - } - /** - * - */ - public set scrollBehavior(scrollBehavior: string) { - this.setProperty('scroll-behavior', scrollBehavior); - } - - /** - * - */ - public get scrollMargin(): string { - return this.getPropertyValue('scroll-margin'); - } - /** - * - */ - public set scrollMargin(scrollMargin: string) { - this.setProperty('scroll-margin', scrollMargin); - } - - /** - * - */ - public get scrollMarginBlock(): string { - return this.getPropertyValue('scroll-margin-block'); - } - /** - * - */ - public set scrollMarginBlock(scrollMarginBlock: string) { - this.setProperty('scroll-margin-block', scrollMarginBlock); - } - - /** - * - */ - public get scrollMarginBlockEnd(): string { - return this.getPropertyValue('scroll-margin-block-end'); - } - /** - * - */ - public set scrollMarginBlockEnd(scrollMarginBlockEnd: string) { - this.setProperty('scroll-margin-block-end', scrollMarginBlockEnd); - } - - /** - * - */ - public get scrollMarginBlockStart(): string { - return this.getPropertyValue('scroll-margin-block-start'); - } - /** - * - */ - public set scrollMarginBlockStart(scrollMarginBlockStart: string) { - this.setProperty('scroll-margin-block-start', scrollMarginBlockStart); - } - - /** - * - */ - public get scrollMarginBottom(): string { - return this.getPropertyValue('scroll-margin-bottom'); - } - /** - * - */ - public set scrollMarginBottom(scrollMarginBottom: string) { - this.setProperty('scroll-margin-bottom', scrollMarginBottom); - } - - /** - * - */ - public get scrollMarginInline(): string { - return this.getPropertyValue('scroll-margin-inline'); - } - /** - * - */ - public set scrollMarginInline(scrollMarginInline: string) { - this.setProperty('scroll-margin-inline', scrollMarginInline); - } - - /** - * - */ - public get scrollMarginInlineEnd(): string { - return this.getPropertyValue('scroll-margin-inline-end'); - } - /** - * - */ - public set scrollMarginInlineEnd(scrollMarginInlineEnd: string) { - this.setProperty('scroll-margin-inline-end', scrollMarginInlineEnd); - } - - /** - * - */ - public get scrollMarginInlineStart(): string { - return this.getPropertyValue('scroll-margin-inline-start'); - } - /** - * - */ - public set scrollMarginInlineStart(scrollMarginInlineStart: string) { - this.setProperty('scroll-margin-inline-start', scrollMarginInlineStart); - } - - /** - * - */ - public get scrollMarginLeft(): string { - return this.getPropertyValue('scroll-margin-left'); - } - /** - * - */ - public set scrollMarginLeft(scrollMarginLeft: string) { - this.setProperty('scroll-margin-left', scrollMarginLeft); - } - - /** - * - */ - public get scrollMarginRight(): string { - return this.getPropertyValue('scroll-margin-right'); - } - /** - * - */ - public set scrollMarginRight(scrollMarginRight: string) { - this.setProperty('scroll-margin-right', scrollMarginRight); - } - - /** - * - */ - public get scrollMarginTop(): string { - return this.getPropertyValue('scroll-margin-top'); - } - /** - * - */ - public set scrollMarginTop(scrollMarginTop: string) { - this.setProperty('scroll-margin-top', scrollMarginTop); - } - - /** - * - */ - public get scrollPadding(): string { - return this.getPropertyValue('scroll-padding'); - } - /** - * - */ - public set scrollPadding(scrollPadding: string) { - this.setProperty('scroll-padding', scrollPadding); - } - - /** - * - */ - public get scrollPaddingBlock(): string { - return this.getPropertyValue('scroll-padding-block'); - } - /** - * - */ - public set scrollPaddingBlock(scrollPaddingBlock: string) { - this.setProperty('scroll-padding-block', scrollPaddingBlock); - } - - /** - * - */ - public get scrollPaddingBlockEnd(): string { - return this.getPropertyValue('scroll-padding-block-end'); - } - /** - * - */ - public set scrollPaddingBlockEnd(scrollPaddingBlockEnd: string) { - this.setProperty('scroll-padding-block-end', scrollPaddingBlockEnd); - } - - /** - * - */ - public get scrollPaddingBlockStart(): string { - return this.getPropertyValue('scroll-padding-block-start'); - } - /** - * - */ - public set scrollPaddingBlockStart(scrollPaddingBlockStart: string) { - this.setProperty('scroll-padding-block-start', scrollPaddingBlockStart); - } - - /** - * - */ - public get scrollPaddingBottom(): string { - return this.getPropertyValue('scroll-padding-bottom'); - } - /** - * - */ - public set scrollPaddingBottom(scrollPaddingBottom: string) { - this.setProperty('scroll-padding-bottom', scrollPaddingBottom); - } - - /** - * - */ - public get scrollPaddingInline(): string { - return this.getPropertyValue('scroll-padding-inline'); - } - /** - * - */ - public set scrollPaddingInline(scrollPaddingInline: string) { - this.setProperty('scroll-padding-inline', scrollPaddingInline); - } - - /** - * - */ - public get scrollPaddingInlineEnd(): string { - return this.getPropertyValue('scroll-padding-inline-end'); - } - /** - * - */ - public set scrollPaddingInlineEnd(scrollPaddingInlineEnd: string) { - this.setProperty('scroll-padding-inline-end', scrollPaddingInlineEnd); - } - - /** - * - */ - public get scrollPaddingInlineStart(): string { - return this.getPropertyValue('scroll-padding-inline-start'); - } - /** - * - */ - public set scrollPaddingInlineStart(scrollPaddingInlineStart: string) { - this.setProperty('scroll-padding-inline-start', scrollPaddingInlineStart); - } - - /** - * - */ - public get scrollPaddingLeft(): string { - return this.getPropertyValue('scroll-padding-left'); - } - /** - * - */ - public set scrollPaddingLeft(scrollPaddingLeft: string) { - this.setProperty('scroll-padding-left', scrollPaddingLeft); - } - - /** - * - */ - public get scrollPaddingRight(): string { - return this.getPropertyValue('scroll-padding-right'); - } - /** - * - */ - public set scrollPaddingRight(scrollPaddingRight: string) { - this.setProperty('scroll-padding-right', scrollPaddingRight); - } - - /** - * - */ - public get scrollPaddingTop(): string { - return this.getPropertyValue('scroll-padding-top'); - } - /** - * - */ - public set scrollPaddingTop(scrollPaddingTop: string) { - this.setProperty('scroll-padding-top', scrollPaddingTop); - } - - /** - * - */ - public get scrollSnapAlign(): string { - return this.getPropertyValue('scroll-snap-align'); - } - /** - * - */ - public set scrollSnapAlign(scrollSnapAlign: string) { - this.setProperty('scroll-snap-align', scrollSnapAlign); - } - - /** - * - */ - public get scrollSnapStop(): string { - return this.getPropertyValue('scroll-snap-stop'); - } - /** - * - */ - public set scrollSnapStop(scrollSnapStop: string) { - this.setProperty('scroll-snap-stop', scrollSnapStop); - } - - /** - * - */ - public get scrollSnapType(): string { - return this.getPropertyValue('scroll-snap-type'); - } - /** - * - */ - public set scrollSnapType(scrollSnapType: string) { - this.setProperty('scroll-snap-type', scrollSnapType); - } - - /** - * - */ - public get shapeImageThreshold(): string { - return this.getPropertyValue('shape-image-threshold'); - } - /** - * - */ - public set shapeImageThreshold(shapeImageThreshold: string) { - this.setProperty('shape-image-threshold', shapeImageThreshold); - } - - /** - * - */ - public get shapeMargin(): string { - return this.getPropertyValue('shape-margin'); - } - /** - * - */ - public set shapeMargin(shapeMargin: string) { - this.setProperty('shape-margin', shapeMargin); - } - - /** - * - */ - public get shapeOutside(): string { - return this.getPropertyValue('shape-outside'); - } - /** - * - */ - public set shapeOutside(shapeOutside: string) { - this.setProperty('shape-outside', shapeOutside); - } - - /** - * - */ - public get shapeRendering(): string { - return this.getPropertyValue('shape-rendering'); - } - /** - * - */ - public set shapeRendering(shapeRendering: string) { - this.setProperty('shape-rendering', shapeRendering); - } - - /** - * - */ - public get size(): string { - return this.getPropertyValue('size'); - } - /** - * - */ - public set size(size: string) { - this.setProperty('size', size); - } - - /** - * - */ - public get speak(): string { - return this.getPropertyValue('speak'); - } - /** - * - */ - public set speak(speak: string) { - this.setProperty('speak', speak); - } - - /** - * - */ - public get src(): string { - return this.getPropertyValue('src'); - } - /** - * - */ - public set src(src: string) { - this.setProperty('src', src); - } - - /** - * - */ - public get stopColor(): string { - return this.getPropertyValue('stop-color'); - } - /** - * - */ - public set stopColor(stopColor: string) { - this.setProperty('stop-color', stopColor); - } - - /** - * - */ - public get stopOpacity(): string { - return this.getPropertyValue('stop-opacity'); - } - /** - * - */ - public set stopOpacity(stopOpacity: string) { - this.setProperty('stop-opacity', stopOpacity); - } - - /** - * - */ - public get stroke(): string { - return this.getPropertyValue('stroke'); - } - /** - * - */ - public set stroke(stroke: string) { - this.setProperty('stroke', stroke); - } - - /** - * - */ - public get strokeDasharray(): string { - return this.getPropertyValue('stroke-dasharray'); - } - /** - * - */ - public set strokeDasharray(strokeDasharray: string) { - this.setProperty('stroke-dasharray', strokeDasharray); - } - - /** - * - */ - public get strokeDashoffset(): string { - return this.getPropertyValue('stroke-dashoffset'); - } - /** - * - */ - public set strokeDashoffset(strokeDashoffset: string) { - this.setProperty('stroke-dashoffset', strokeDashoffset); - } - - /** - * - */ - public get strokeLinecap(): string { - return this.getPropertyValue('stroke-linecap'); - } - /** - * - */ - public set strokeLinecap(strokeLinecap: string) { - this.setProperty('stroke-linecap', strokeLinecap); - } - - /** - * - */ - public get strokeLinejoin(): string { - return this.getPropertyValue('stroke-linejoin'); - } - /** - * - */ - public set strokeLinejoin(strokeLinejoin: string) { - this.setProperty('stroke-linejoin', strokeLinejoin); - } - - /** - * - */ - public get strokeMiterlimit(): string { - return this.getPropertyValue('stroke-miterlimit'); - } - /** - * - */ - public set strokeMiterlimit(strokeMiterlimit: string) { - this.setProperty('stroke-miterlimit', strokeMiterlimit); - } - - /** - * - */ - public get strokeOpacity(): string { - return this.getPropertyValue('stroke-opacity'); - } - /** - * - */ - public set strokeOpacity(strokeOpacity: string) { - this.setProperty('stroke-opacity', strokeOpacity); - } - - /** - * - */ - public get strokeWidth(): string { - return this.getPropertyValue('stroke-width'); - } - /** - * - */ - public set strokeWidth(strokeWidth: string) { - this.setProperty('stroke-width', strokeWidth); - } - - /** - * - */ - public get syntax(): string { - return this.getPropertyValue('syntax'); - } - /** - * - */ - public set syntax(syntax: string) { - this.setProperty('syntax', syntax); - } - - /** - * - */ - public get tabSize(): string { - return this.getPropertyValue('tab-size'); - } - /** - * - */ - public set tabSize(tabSize: string) { - this.setProperty('tab-size', tabSize); - } - - /** - * - */ - public get tableLayout(): string { - return this.getPropertyValue('table-layout'); - } - /** - * - */ - public set tableLayout(tableLayout: string) { - this.setProperty('table-layout', tableLayout); - } - - /** - * - */ - public get textAlign(): string { - return this.getPropertyValue('text-align'); - } - /** - * - */ - public set textAlign(textAlign: string) { - this.setProperty('text-align', textAlign); - } - - /** - * - */ - public get textAlignLast(): string { - return this.getPropertyValue('text-align-last'); - } - /** - * - */ - public set textAlignLast(textAlignLast: string) { - this.setProperty('text-align-last', textAlignLast); - } - - /** - * - */ - public get textAnchor(): string { - return this.getPropertyValue('text-anchor'); - } - /** - * - */ - public set textAnchor(textAnchor: string) { - this.setProperty('text-anchor', textAnchor); - } - - /** - * - */ - public get textCombineUpright(): string { - return this.getPropertyValue('text-combine-upright'); - } - /** - * - */ - public set textCombineUpright(textCombineUpright: string) { - this.setProperty('text-combine-upright', textCombineUpright); - } - - /** - * - */ - public get textDecoration(): string { - return this.getPropertyValue('text-decoration'); - } - /** - * - */ - public set textDecoration(textDecoration: string) { - this.setProperty('text-decoration', textDecoration); - } - - /** - * - */ - public get textDecorationColor(): string { - return this.getPropertyValue('text-decoration-color'); - } - /** - * - */ - public set textDecorationColor(textDecorationColor: string) { - this.setProperty('text-decoration-color', textDecorationColor); - } - - /** - * - */ - public get textDecorationLine(): string { - return this.getPropertyValue('text-decoration-line'); - } - /** - * - */ - public set textDecorationLine(textDecorationLine: string) { - this.setProperty('text-decoration-line', textDecorationLine); - } - - /** - * - */ - public get textDecorationSkipInk(): string { - return this.getPropertyValue('text-decoration-skip-ink'); - } - /** - * - */ - public set textDecorationSkipInk(textDecorationSkipInk: string) { - this.setProperty('text-decoration-skip-ink', textDecorationSkipInk); - } - - /** - * - */ - public get textDecorationStyle(): string { - return this.getPropertyValue('text-decoration-style'); - } - /** - * - */ - public set textDecorationStyle(textDecorationStyle: string) { - this.setProperty('text-decoration-style', textDecorationStyle); - } - - /** - * - */ - public get textIndent(): string { - return this.getPropertyValue('text-indent'); - } - /** - * - */ - public set textIndent(textIndent: string) { - this.setProperty('text-indent', textIndent); - } - - /** - * - */ - public get textOrientation(): string { - return this.getPropertyValue('text-orientation'); - } - /** - * - */ - public set textOrientation(textOrientation: string) { - this.setProperty('text-orientation', textOrientation); - } - - /** - * - */ - public get textOverflow(): string { - return this.getPropertyValue('text-overflow'); - } - /** - * - */ - public set textOverflow(textOverflow: string) { - this.setProperty('text-overflow', textOverflow); - } - - /** - * - */ - public get textRendering(): string { - return this.getPropertyValue('text-rendering'); - } - /** - * - */ - public set textRendering(textRendering: string) { - this.setProperty('text-rendering', textRendering); - } - - /** - * - */ - public get textShadow(): string { - return this.getPropertyValue('text-shadow'); - } - /** - * - */ - public set textShadow(textShadow: string) { - this.setProperty('text-shadow', textShadow); - } - - /** - * - */ - public get textSizeAdjust(): string { - return this.getPropertyValue('text-size-adjust'); - } - /** - * - */ - public set textSizeAdjust(textSizeAdjust: string) { - this.setProperty('text-size-adjust', textSizeAdjust); - } - - /** - * - */ - public get textTransform(): string { - return this.getPropertyValue('text-transform'); - } - /** - * - */ - public set textTransform(textTransform: string) { - this.setProperty('text-transform', textTransform); - } - - /** - * - */ - public get textUnderlinePosition(): string { - return this.getPropertyValue('text-underline-position'); - } - /** - * - */ - public set textUnderlinePosition(textUnderlinePosition: string) { - this.setProperty('text-underline-position', textUnderlinePosition); - } - - /** - * - */ - public get top(): string { - return this.getPropertyValue('top'); - } - /** - * - */ - public set top(top: string) { - this.setProperty('top', top); - } - - /** - * - */ - public get touchAction(): string { - return this.getPropertyValue('touch-action'); - } - /** - * - */ - public set touchAction(touchAction: string) { - this.setProperty('touch-action', touchAction); - } - - /** - * - */ - public get transform(): string { - return this.getPropertyValue('transform'); - } - /** - * - */ - public set transform(transform: string) { - this.setProperty('transform', transform); - } - - /** - * - */ - public get transformBox(): string { - return this.getPropertyValue('transform-box'); - } - /** - * - */ - public set transformBox(transformBox: string) { - this.setProperty('transform-box', transformBox); - } - - /** - * - */ - public get transformOrigin(): string { - return this.getPropertyValue('transform-origin'); - } - /** - * - */ - public set transformOrigin(transformOrigin: string) { - this.setProperty('transform-origin', transformOrigin); - } - - /** - * - */ - public get transformStyle(): string { - return this.getPropertyValue('transform-style'); - } - /** - * - */ - public set transformStyle(transformStyle: string) { - this.setProperty('transform-style', transformStyle); - } - - /** - * - */ - public get transition(): string { - return this.getPropertyValue('transition'); - } - /** - * - */ - public set transition(transition: string) { - this.setProperty('transition', transition); - } - - /** - * - */ - public get transitionDelay(): string { - return this.getPropertyValue('transition-delay'); - } - /** - * - */ - public set transitionDelay(transitionDelay: string) { - this.setProperty('transition-delay', transitionDelay); - } - - /** - * - */ - public get transitionDuration(): string { - return this.getPropertyValue('transition-duration'); - } - /** - * - */ - public set transitionDuration(transitionDuration: string) { - this.setProperty('transition-duration', transitionDuration); - } - - /** - * - */ - public get transitionProperty(): string { - return this.getPropertyValue('transition-property'); - } - /** - * - */ - public set transitionProperty(transitionProperty: string) { - this.setProperty('transition-property', transitionProperty); - } - - /** - * - */ - public get transitionTimingFunction(): string { - return this.getPropertyValue('transition-timing-function'); - } - /** - * - */ - public set transitionTimingFunction(transitionTimingFunction: string) { - this.setProperty('transition-timing-function', transitionTimingFunction); - } - - /** - * - */ - public get unicodeBidi(): string { - return this.getPropertyValue('unicode-bidi'); - } - /** - * - */ - public set unicodeBidi(unicodeBidi: string) { - this.setProperty('unicode-bidi', unicodeBidi); - } - - /** - * - */ - public get unicodeRange(): string { - return this.getPropertyValue('unicode-range'); - } - /** - * - */ - public set unicodeRange(unicodeRange: string) { - this.setProperty('unicode-range', unicodeRange); - } - - /** - * - */ - public get userSelect(): string { - return this.getPropertyValue('user-select'); - } - /** - * - */ - public set userSelect(userSelect: string) { - this.setProperty('user-select', userSelect); - } - - /** - * - */ - public get userZoom(): string { - return this.getPropertyValue('user-zoom'); - } - /** - * - */ - public set userZoom(userZoom: string) { - this.setProperty('user-zoom', userZoom); - } - - /** - * - */ - public get vectorEffect(): string { - return this.getPropertyValue('vector-effect'); - } - /** - * - */ - public set vectorEffect(vectorEffect: string) { - this.setProperty('vector-effect', vectorEffect); - } - - /** - * - */ - public get verticalAlign(): string { - return this.getPropertyValue('vertical-align'); - } - /** - * - */ - public set verticalAlign(verticalAlign: string) { - this.setProperty('vertical-align', verticalAlign); - } - - /** - * - */ - public get visibility(): string { - return this.getPropertyValue('visibility'); - } - /** - * - */ - public set visibility(visibility: string) { - this.setProperty('visibility', visibility); - } - - /** - * - */ - public get whiteSpace(): string { - return this.getPropertyValue('white-space'); - } - /** - * - */ - public set whiteSpace(whiteSpace: string) { - this.setProperty('white-space', whiteSpace); - } - - /** - * - */ - public get widows(): string { - return this.getPropertyValue('widows'); - } - /** - * - */ - public set widows(widows: string) { - this.setProperty('widows', widows); - } - - /** - * - */ - public get width(): string { - return this.getPropertyValue('width'); - } - /** - * - */ - public set width(width: string) { - this.setProperty('width', width); - } - - /** - * - */ - public get willChange(): string { - return this.getPropertyValue('will-change'); - } - /** - * - */ - public set willChange(willChange: string) { - this.setProperty('will-change', willChange); - } - - /** - * - */ - public get wordBreak(): string { - return this.getPropertyValue('word-break'); - } - /** - * - */ - public set wordBreak(wordBreak: string) { - this.setProperty('word-break', wordBreak); - } - - /** - * - */ - public get wordSpacing(): string { - return this.getPropertyValue('word-spacing'); - } - /** - * - */ - public set wordSpacing(wordSpacing: string) { - this.setProperty('word-spacing', wordSpacing); - } - - /** - * - */ - public get wordWrap(): string { - return this.getPropertyValue('word-wrap'); - } - /** - * - */ - public set wordWrap(wordWrap: string) { - this.setProperty('word-wrap', wordWrap); - } - - /** - * - */ - public get writingMode(): string { - return this.getPropertyValue('writing-mode'); - } - /** - * - */ - public set writingMode(writingMode: string) { - this.setProperty('writing-mode', writingMode); - } - - /** - * - */ - public get x(): string { - return this.getPropertyValue('x'); - } - /** - * - */ - public set x(x: string) { - this.setProperty('x', x); - } - - /** - * - */ - public get y(): string { - return this.getPropertyValue('y'); - } - /** - * - */ - public set y(y: string) { - this.setProperty('y', y); - } - - /** - * - */ - public get zIndex(): string { - return this.getPropertyValue('z-index'); - } - /** - * - */ - public set zIndex(zIndex: string) { - this.setProperty('z-index', zIndex); - } - - /** - * - */ - public get zoom(): string { - return this.getPropertyValue('zoom'); - } - /** - * - */ - public set zoom(zoom: string) { - this.setProperty('zoom', zoom); - } - - /** - * Returns the style decleration as a CSS text. - * - * @returns CSS text. - */ - public get cssText(): string { - const style = this._attributes['style']; - if (style && style.value) { - return style.value; - } - return ''; - } - - /** - * Sets CSS text. - * - * @param cssText CSS text. - */ - public set cssText(cssText: string) { - if (cssText) { - if (!this._attributes['style']) { - this._attributes['style'] = new Attr(); - this._attributes['style'].name = 'style'; - } - const parts = cssText.split(';'); - const newStyle = []; - let index = 0; - for (let i = 0; i < this.length; i++) { - delete this[i]; - } - for (const part of parts) { - if (part) { - const [name, value] = part.trim().split(':'); - if (value) { - newStyle.push(`${name}: ${value.trim()};`); - } else { - newStyle.push(name); - } - this[index] = name; - index++; - } - } - (this.length) = index; - this._attributes['style'].value = newStyle.join(' '); - } else { - delete this._attributes['style']; - for (let i = 0; i < this.length; i++) { - delete this[i]; - } - (this.length) = 0; - } - } - - /** - * Returns item. - * - * @param index Index. - * @returns Item. - */ - public item(index: number): string { - return this[index] || ''; - } - - /** - * Set a property. - * - * @param propertyName 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 = ''): void { - if (!value) { - this.removeProperty(propertyName); - return; - } - - if (!this._attributes['style']) { - this._attributes['style'] = new Attr(); - this._attributes['style'].name = 'style'; - } - - const style = this._attributes['style']; - const newStyle = []; - let index = 0; - let isExisting = false; - - if (style && style.value) { - const parts = style.value.split(';'); - for (const part of parts) { - if (part) { - const [name, existingValue] = part.trim().split(':'); - if (name === propertyName) { - newStyle.push(`${name}: ${value};`); - isExisting = true; - } else if (existingValue) { - newStyle.push(`${name}: ${existingValue.trim()};`); - } else { - newStyle.push(`${name};`); - } - - this[index] = name; - index++; - } - } - } - - if (!isExisting) { - newStyle.push(`${propertyName}: ${value}${priority ? '' + priority : ''};`); - this[index] = propertyName; - index++; - } - - this._attributes['style'].value = newStyle.join(' '); - (this.length) = index; - } - - /** - * Removes a property. - * - * @param propertyName 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 { - const style = this._attributes['style']; - const newStyle = []; - let hasProperty = false; - let index = 0; - - if (style && style.value) { - const parts = style.value.split(';'); - for (const part of parts) { - if (part) { - const [name, value] = part.trim().split(':'); - if (name !== propertyName) { - newStyle.push(`${name}: ${value.trim()};`); - this[index] = name; - index++; - hasProperty = true; - } - } - } - } - - if (newStyle.length) { - this._attributes['style'].value = newStyle.join(' '); - } else { - delete this._attributes['style']; - } - - if (hasProperty) { - delete this[index]; - } - - (this.length) = index; - } - - /** - * Returns a property. - * - * @param propertyName Property name in kebab case. - * @returns Property value. - */ - public getPropertyValue(propertyName: string): string { - if (this._computedStyleElement && !this._computedStyleElement.isConnected) { - return ''; - } - - const style = this._attributes['style']; - if (style && style.value) { - const parts = style.value.split(';'); - for (const part of parts) { - if (part) { - const [name, value] = part.trim().split(':'); - if (name === propertyName) { - if (!value) { - return ''; - } - return value.trim(); - } - } - } - } - return ''; - } -} diff --git a/packages/happy-dom/src/css/computed-style/ComputedStyle.ts b/packages/happy-dom/src/css/computed-style/ComputedStyle.ts new file mode 100644 index 000000000..c0ae5e8fa --- /dev/null +++ b/packages/happy-dom/src/css/computed-style/ComputedStyle.ts @@ -0,0 +1,54 @@ +import IElement from '../../nodes/element/IElement'; +import ComputedStylePropertyParser from './ComputedStylePropertyParser'; +import CSSStyleDeclarationDefaultValues from './config/CSSStyleDeclarationDefaultValues'; +import CSSStyleDeclarationNodeDefaultValues from './config/CSSStyleDeclarationNodeDefaultValues'; +import CSSStyleDeclaration from '../declaration/CSSStyleDeclaration'; +import CSSStyleDeclarationUtility from '../declaration/CSSStyleDeclarationUtility'; + +/** + * Computed styles. + */ +export default class ComputedStyle { + /** + * Converts style string to object. + * + * @param element Element. + * @returns Style object. + */ + public static getComputedStyle(element: IElement): CSSStyleDeclaration { + const cssStyleDeclaration = new CSSStyleDeclaration(); + const styles = this.getStyles(element); + + for (const key of Object.keys(styles)) { + cssStyleDeclaration.setProperty(key, styles[key]); + } + + cssStyleDeclaration._readonly = true; + + return cssStyleDeclaration; + } + + /** + * Returns property styles for element. + * + * @param element Element. + * @returns Styles. + */ + private static getStyles(element: IElement): { [k: string]: string } { + const styles = {}; + + if (element['_attributes']['style'] && element['_attributes']['style'].value) { + const styleProperty = CSSStyleDeclarationUtility.styleStringToObject( + element['_attributes']['style'].value + ); + for (const key of Object.keys(styleProperty)) { + Object.assign(styles, ComputedStylePropertyParser.parseProperty(key, styleProperty[key])); + } + } + return Object.assign( + CSSStyleDeclarationDefaultValues, + CSSStyleDeclarationNodeDefaultValues[element.tagName], + styles + ); + } +} diff --git a/packages/happy-dom/src/css/computed-style/ComputedStylePropertyParser.ts b/packages/happy-dom/src/css/computed-style/ComputedStylePropertyParser.ts new file mode 100644 index 000000000..0faab3ae1 --- /dev/null +++ b/packages/happy-dom/src/css/computed-style/ComputedStylePropertyParser.ts @@ -0,0 +1,163 @@ +/** + * Computed style property parser. + */ +export default class ComputedStylePropertyParser { + /** + * Parses a style property. + * + * @param name Name. + * @param value Value. + * @returns Property value. + */ + public static parseProperty(name: string, value: string): { [k: string]: string } { + switch (name) { + case 'border': + const borderParts = value.split(/ +/); + + if (borderParts.length < 2) { + return {}; + } + + const borderWidth = this.toPixels(borderParts[0]); + const borderColor = this.toPixels(borderParts[0]); + + return { + border: `${borderWidth} ${borderParts[1]} ${borderColor}`, + 'border-top': `${borderWidth} ${borderParts[1]} ${borderColor}`, + 'border-bottom': `${borderWidth} ${borderParts[1]} ${borderColor}`, + 'border-left': `${borderWidth} ${borderParts[1]} ${borderColor}`, + 'border-right': `${borderWidth} ${borderParts[1]} ${borderColor}`, + 'border-top-width': borderWidth, + 'border-right-width': borderWidth, + 'border-bottom-width': borderWidth, + 'border-left-width': borderWidth, + 'border-top-style': borderParts[1], + 'border-right-style': borderParts[1], + 'border-bottom-style': borderParts[1], + 'border-left-style': borderParts[1], + 'border-top-color': borderColor, + 'border-right-color': borderColor, + 'border-bottom-color': borderColor, + 'border-left-color': borderColor, + + // TODO: How to parse image from the border value? + 'border-image-source': 'none', + 'border-image-slice': '100%', + 'border-image-width': '1', + 'border-image-outset': '0', + 'border-image-repeat': 'stretch' + }; + case 'border-left': + case 'border-bottom': + case 'border-right': + case 'border-top': + const borderPostionedParts = value.split(/ +/); + + if (borderPostionedParts.length < 2) { + return {}; + } + + const borderName = name.split('-')[1]; + const borderPositionedWidth = this.toPixels(borderPostionedParts[0]); + const borderPositionedColor = borderPostionedParts[2] + ? this.toRGB(borderPostionedParts[2]) + : 'rgb(0, 0, 0)'; + + return { + [`border-${borderName}`]: `${borderPositionedWidth} ${borderPostionedParts[1]} ${borderPositionedColor}`, + [`border-${borderName}-width`]: borderPositionedWidth, + [`border-${borderName}-style`]: borderPostionedParts[1], + [`border-${borderName}-color`]: borderPositionedColor + }; + case 'border-width': + const borderWidthValue = this.toPixels(value); + return { + 'border-top-width': borderWidthValue, + 'border-right-width': borderWidthValue, + 'border-bottom-width': borderWidthValue, + 'border-left-width': borderWidthValue + }; + case 'border-style': + return { + 'border-top-style': value, + 'border-right-style': value, + 'border-bottom-style': value, + 'border-left-style': value + }; + case 'border-color': + const borderColorValue = this.toRGB(value); + return { + 'border-top-color': borderColorValue, + 'border-right-color': borderColorValue, + 'border-bottom-color': borderColorValue, + 'border-left-color': borderColorValue + }; + case 'border-radius': + const borderRadiusParts = value.split(/ +/); + const borderRadiusTopLeftValue = this.toPixels(borderRadiusParts[0]); + const borderRadiusTopRightValue = borderRadiusParts[1] + ? this.toPixels(borderRadiusParts[1]) + : ''; + const borderRadiusBottomRightValue = borderRadiusParts[2] + ? this.toPixels(borderRadiusParts[2]) + : ''; + const borderRadiusBottomLeftValue = borderRadiusParts[3] + ? this.toPixels(borderRadiusParts[3]) + : ''; + return { + 'border-radius': `${borderRadiusTopLeftValue}${ + borderRadiusTopRightValue ? ` ${borderRadiusTopRightValue}` : '' + }${borderRadiusBottomRightValue ? ` ${borderRadiusBottomRightValue}` : ''}${ + borderRadiusBottomLeftValue ? ` ${borderRadiusBottomLeftValue}` : '' + }`, + 'border-top-left-radius': borderRadiusTopLeftValue || borderRadiusTopLeftValue, + 'border-top-right-radius': borderRadiusTopRightValue || borderRadiusTopLeftValue, + 'border-bottom-right-radius': borderRadiusBottomRightValue || borderRadiusTopLeftValue, + 'border-bottom-left-radius': + borderRadiusBottomLeftValue || borderRadiusTopRightValue || borderRadiusTopLeftValue + }; + case 'padding': + case 'margin': + const paddingParts = value.split(/ +/); + const paddingTopValue = this.toPixels(paddingParts[0]); + const paddingRightValue = paddingParts[1] ? this.toPixels(paddingParts[1]) : ''; + const paddingBottomValue = paddingParts[2] ? this.toPixels(paddingParts[2]) : ''; + const paddingLeftValue = paddingParts[2] ? this.toPixels(paddingParts[2]) : ''; + return { + [name]: `${paddingTopValue}${paddingRightValue ? ` ${paddingRightValue}` : ''}${ + paddingBottomValue ? ` ${paddingBottomValue}` : '' + }${paddingLeftValue ? ` ${paddingLeftValue}` : ''}`, + [`${name}-top`]: paddingTopValue, + [`${name}-right`]: paddingRightValue || paddingParts[0], + [`${name}-bottom`]: paddingBottomValue || paddingParts[0], + [`${name}-left`]: paddingLeftValue || paddingParts[1] || paddingParts[0] + }; + } + + return { + [name]: value + }; + } + + /** + * Returns value in pixels. + * + * @param value Value. + * @returns Value in pixels. + */ + private static toPixels(value: string): string { + // TODO: Fix convertion to pixels + return value; + } + + /** + * Returns value in pixels. + * + * @param value Value. + * @returns Value in RGB. + */ + private static toRGB(value: string): string { + // TODO: Fix convertion to RGB + return value; + } +} diff --git a/packages/happy-dom/src/css/computed-style/config/CSSStyleDeclarationDefaultValues.ts b/packages/happy-dom/src/css/computed-style/config/CSSStyleDeclarationDefaultValues.ts new file mode 100644 index 000000000..335e05b30 --- /dev/null +++ b/packages/happy-dom/src/css/computed-style/config/CSSStyleDeclarationDefaultValues.ts @@ -0,0 +1,295 @@ +export default { + 'accent-color': 'auto', + 'align-content': 'normal', + 'align-items': 'normal', + 'align-self': 'auto', + 'alignment-baseline': 'auto', + 'animation-delay': '0s', + 'animation-direction': 'normal', + 'animation-duration': '0s', + 'animation-fill-mode': 'none', + 'animation-iteration-count': '1', + 'animation-name': 'none', + 'animation-play-state': 'running', + 'animation-timing-function': 'ease', + 'app-region': 'none', + appearance: 'none', + 'backdrop-filter': 'none', + 'backface-visibility': 'visible', + 'background-attachment': 'scroll', + 'background-blend-mode': 'normal', + 'background-clip': 'border-box', + 'background-color': 'rgba(0, 0, 0, 0)', + 'background-image': 'none', + 'background-origin': 'padding-box', + 'background-position': '0% 0%', + 'background-repeat': 'repeat', + 'background-size': 'auto', + 'baseline-shift': '0px', + 'block-size': 'auto', + 'border-block-end-color': 'rgb(0, 0, 0)', + 'border-block-end-style': 'none', + 'border-block-end-width': '0px', + 'border-block-start-color': 'rgb(0, 0, 0)', + 'border-block-start-style': 'none', + 'border-block-start-width': '0px', + 'border-bottom-color': 'rgb(0, 0, 0)', + 'border-bottom-left-radius': '0px', + 'border-bottom-right-radius': '0px', + 'border-bottom-style': 'none', + 'border-bottom-width': '0px', + 'border-collapse': 'separate', + 'border-end-end-radius': '0px', + 'border-end-start-radius': '0px', + 'border-image-outset': '0', + 'border-image-repeat': 'stretch', + 'border-image-slice': '100%', + 'border-image-source': 'none', + 'border-image-width': '1', + 'border-inline-end-color': 'rgb(0, 0, 0)', + 'border-inline-end-style': 'none', + 'border-inline-end-width': '0px', + 'border-inline-start-color': 'rgb(0, 0, 0)', + 'border-inline-start-style': 'none', + 'border-inline-start-width': '0px', + 'border-left-color': 'rgb(0, 0, 0)', + 'border-left-style': 'none', + 'border-left-width': '0px', + 'border-right-color': 'rgb(0, 0, 0)', + 'border-right-style': 'none', + 'border-right-width': '0px', + 'border-start-end-radius': '0px', + 'border-start-start-radius': '0px', + 'border-top-color': 'rgb(0, 0, 0)', + 'border-top-left-radius': '0px', + 'border-top-right-radius': '0px', + 'border-top-style': 'none', + 'border-top-width': '0px', + bottom: 'auto', + 'box-shadow': 'none', + 'box-sizing': 'content-box', + 'break-after': 'auto', + 'break-before': 'auto', + 'break-inside': 'auto', + 'buffered-rendering': 'auto', + 'caption-side': 'top', + 'caret-color': 'rgb(0, 0, 0)', + clear: 'none', + clip: 'auto', + 'clip-path': 'none', + 'clip-rule': 'nonzero', + color: 'rgb(0, 0, 0)', + 'color-interpolation': 'srgb', + 'color-interpolation-filters': 'linearrgb', + 'color-rendering': 'auto', + 'column-count': 'auto', + 'column-gap': 'normal', + 'column-rule-color': 'rgb(0, 0, 0)', + 'column-rule-style': 'none', + 'column-rule-width': '0px', + 'column-span': 'none', + 'column-width': 'auto', + 'contain-intrinsic-block-size': 'none', + 'contain-intrinsic-height': 'none', + 'contain-intrinsic-inline-size': 'none', + 'contain-intrinsic-size': 'none', + 'contain-intrinsic-width': 'none', + content: 'normal', + cursor: 'auto', + cx: '0px', + cy: '0px', + d: 'none', + direction: 'ltr', + display: 'inline', + 'dominant-baseline': 'auto', + 'empty-cells': 'show', + fill: 'rgb(0, 0, 0)', + 'fill-opacity': '1', + 'fill-rule': 'nonzero', + filter: 'none', + 'flex-basis': 'auto', + 'flex-direction': 'row', + 'flex-grow': '0', + 'flex-shrink': '1', + 'flex-wrap': 'nowrap', + float: 'none', + 'flood-color': 'rgb(0, 0, 0)', + 'flood-opacity': '1', + 'font-family': 'Roboto, system-ui, sans-serif', + 'font-kerning': 'auto', + 'font-optical-sizing': 'auto', + 'font-palette': 'normal', + 'font-size': '13px', + 'font-stretch': '100%', + 'font-style': 'normal', + 'font-synthesis-small-caps': 'auto', + 'font-synthesis-style': 'auto', + 'font-synthesis-weight': 'auto', + 'font-variant': 'normal', + 'font-variant-caps': 'normal', + 'font-variant-east-asian': 'normal', + 'font-variant-ligatures': 'normal', + 'font-variant-numeric': 'normal', + 'font-weight': '400', + 'grid-auto-columns': 'auto', + 'grid-auto-flow': 'row', + 'grid-auto-rows': 'auto', + 'grid-column-end': 'auto', + 'grid-column-start': 'auto', + 'grid-row-end': 'auto', + 'grid-row-start': 'auto', + 'grid-template-areas': 'none', + 'grid-template-columns': 'none', + 'grid-template-rows': 'none', + height: 'auto', + hyphens: 'manual', + 'image-orientation': 'from-image', + 'image-rendering': 'auto', + 'inline-size': 'auto', + 'inset-block-end': 'auto', + 'inset-block-start': 'auto', + 'inset-inline-end': 'auto', + 'inset-inline-start': 'auto', + isolation: 'auto', + 'justify-content': 'normal', + 'justify-items': 'normal', + 'justify-self': 'auto', + left: 'auto', + 'letter-spacing': 'normal', + 'lighting-color': 'rgb(255, 255, 255)', + 'line-break': 'auto', + 'line-height': 'normal', + 'list-style-image': 'none', + 'list-style-position': 'outside', + 'list-style-type': 'disc', + 'margin-block-end': '0px', + 'margin-block-start': '0px', + 'margin-bottom': '0px', + 'margin-inline-end': '0px', + 'margin-inline-start': '0px', + 'margin-left': '0px', + 'margin-right': '0px', + 'margin-top': '0px', + 'marker-end': 'none', + 'marker-mid': 'none', + 'marker-start': 'none', + 'mask-type': 'luminance', + 'max-block-size': 'none', + 'max-height': 'none', + 'max-inline-size': 'none', + 'max-width': 'none', + 'min-block-size': '0px', + 'min-height': '0px', + 'min-inline-size': '0px', + 'min-width': '0px', + 'mix-blend-mode': 'normal', + 'object-fit': 'fill', + 'object-position': '50% 50%', + 'offset-distance': '0px', + 'offset-path': 'none', + 'offset-rotate': 'auto 0deg', + opacity: '1', + order: '0', + orphans: '2', + 'outline-color': 'rgb(0, 0, 0)', + 'outline-offset': '0px', + 'outline-style': 'none', + 'outline-width': '0px', + 'overflow-anchor': 'auto', + 'overflow-clip-margin': '0px', + 'overflow-wrap': 'normal', + 'overflow-x': 'visible', + 'overflow-y': 'visible', + 'overscroll-behavior-block': 'auto', + 'overscroll-behavior-inline': 'auto', + 'padding-block-end': '0px', + 'padding-block-start': '0px', + 'padding-bottom': '0px', + 'padding-inline-end': '0px', + 'padding-inline-start': '0px', + 'padding-left': '0px', + 'padding-right': '0px', + 'padding-top': '0px', + 'paint-order': 'normal', + perspective: 'none', + 'perspective-origin': '0px 0px', + 'pointer-events': 'auto', + position: 'static', + r: '0px', + resize: 'none', + right: 'auto', + 'row-gap': 'normal', + 'ruby-position': 'over', + rx: 'auto', + ry: 'auto', + 'scroll-behavior': 'auto', + 'scroll-margin-block-end': '0px', + 'scroll-margin-block-start': '0px', + 'scroll-margin-inline-end': '0px', + 'scroll-margin-inline-start': '0px', + 'scroll-padding-block-end': 'auto', + 'scroll-padding-block-start': 'auto', + 'scroll-padding-inline-end': 'auto', + 'scroll-padding-inline-start': 'auto', + 'scrollbar-gutter': 'auto', + 'shape-image-threshold': '0', + 'shape-margin': '0px', + 'shape-outside': 'none', + 'shape-rendering': 'auto', + speak: 'normal', + 'stop-color': 'rgb(0, 0, 0)', + 'stop-opacity': '1', + stroke: 'none', + 'stroke-dasharray': 'none', + 'stroke-dashoffset': '0px', + 'stroke-linecap': 'butt', + 'stroke-linejoin': 'miter', + 'stroke-miterlimit': '4', + 'stroke-opacity': '1', + 'stroke-width': '1px', + 'tab-size': '8', + 'table-layout': 'auto', + 'text-align': 'start', + 'text-align-last': 'auto', + 'text-anchor': 'start', + 'text-decoration': 'none solid rgb(0, 0, 0)', + 'text-decoration-color': 'rgb(0, 0, 0)', + 'text-decoration-line': 'none', + 'text-decoration-skip-ink': 'auto', + 'text-decoration-style': 'solid', + 'text-emphasis-color': 'rgb(0, 0, 0)', + 'text-emphasis-position': 'over', + 'text-emphasis-style': 'none', + 'text-indent': '0px', + 'text-overflow': 'clip', + 'text-rendering': 'auto', + 'text-shadow': 'none', + 'text-size-adjust': 'auto', + 'text-transform': 'none', + 'text-underline-position': 'auto', + top: 'auto', + 'touch-action': 'auto', + transform: 'none', + 'transform-origin': '0px 0px', + 'transform-style': 'flat', + 'transition-delay': '0s', + 'transition-duration': '0s', + 'transition-property': 'all', + 'transition-timing-function': 'ease', + 'unicode-bidi': 'normal', + 'user-select': 'auto', + 'vector-effect': 'none', + 'vertical-align': 'baseline', + visibility: 'visible', + 'white-space': 'normal', + widows: '2', + width: 'auto', + 'will-change': 'auto', + 'word-break': 'normal', + 'word-spacing': '0px', + 'writing-mode': 'horizontal-tb', + x: '0px', + y: '0px', + 'z-index': 'auto', + zoom: '1' +}; diff --git a/packages/happy-dom/src/css/computed-style/config/CSSStyleDeclarationNodeDefaultValues.ts b/packages/happy-dom/src/css/computed-style/config/CSSStyleDeclarationNodeDefaultValues.ts new file mode 100644 index 000000000..63df45eb1 --- /dev/null +++ b/packages/happy-dom/src/css/computed-style/config/CSSStyleDeclarationNodeDefaultValues.ts @@ -0,0 +1,1095 @@ +export default { + A: {}, + ABBR: {}, + ADDRESS: { + 'block-size': '0px', + display: 'block', + 'font-style': 'italic', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + AREA: {}, + ARTICLE: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + ASIDE: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + AUDIO: { + 'block-size': '54px', + display: 'none', + height: '54px', + 'inline-size': '300px', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%', + width: '300px' + }, + B: { + 'font-weight': '700' + }, + BASE: { + display: 'none', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + BDI: { + 'unicode-bidi': 'isolate' + }, + BDO: { + 'unicode-bidi': 'bidi-override' + }, + BLOCKQUAOTE: {}, + BODY: { + 'background-color': 'rgb(255, 255, 255)', + 'block-size': '0px', + display: 'block', + 'font-size': '10.5625px', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + TEMPLATE: { + display: 'none', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + FORM: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + INPUT: { + appearance: 'auto', + 'background-color': 'rgb(255, 255, 255)', + 'block-size': '15.5px', + 'border-block-end-color': 'rgb(118, 118, 118)', + 'border-block-end-style': 'inset', + 'border-block-end-width': '2px', + 'border-block-start-color': 'rgb(118, 118, 118)', + 'border-block-start-style': 'inset', + 'border-block-start-width': '2px', + 'border-bottom-color': 'rgb(118, 118, 118)', + 'border-bottom-style': 'inset', + 'border-bottom-width': '2px', + 'border-inline-end-color': 'rgb(118, 118, 118)', + 'border-inline-end-style': 'inset', + 'border-inline-end-width': '2px', + 'border-inline-start-color': 'rgb(118, 118, 118)', + 'border-inline-start-style': 'inset', + 'border-inline-start-width': '2px', + 'border-left-color': 'rgb(118, 118, 118)', + 'border-left-style': 'inset', + 'border-left-width': '2px', + 'border-right-color': 'rgb(118, 118, 118)', + 'border-right-style': 'inset', + 'border-right-width': '2px', + 'border-top-color': 'rgb(118, 118, 118)', + 'border-top-style': 'inset', + 'border-top-width': '2px', + cursor: 'text', + display: 'inline-block', + 'font-family': 'Arial', + 'font-size': '13.3333px', + height: '15.5px', + 'inline-size': '139px', + 'padding-block-end': '1px', + 'padding-block-start': '1px', + 'padding-bottom': '1px', + 'padding-inline-end': '2px', + 'padding-inline-start': '2px', + 'padding-left': '2px', + 'padding-right': '2px', + 'padding-top': '1px', + 'perspective-origin': '73.5px 10.75px', + 'transform-origin': '73.5px 10.75px', + width: '139px' + }, + TEXTAREA: { + appearance: 'auto', + 'background-color': 'rgb(255, 255, 255)', + 'block-size': '31px', + 'border-block-end-color': 'rgb(118, 118, 118)', + 'border-block-end-style': 'solid', + 'border-block-end-width': '1px', + 'border-block-start-color': 'rgb(118, 118, 118)', + 'border-block-start-style': 'solid', + 'border-block-start-width': '1px', + 'border-bottom-color': 'rgb(118, 118, 118)', + 'border-bottom-style': 'solid', + 'border-bottom-width': '1px', + 'border-inline-end-color': 'rgb(118, 118, 118)', + 'border-inline-end-style': 'solid', + 'border-inline-end-width': '1px', + 'border-inline-start-color': 'rgb(118, 118, 118)', + 'border-inline-start-style': 'solid', + 'border-inline-start-width': '1px', + 'border-left-color': 'rgb(118, 118, 118)', + 'border-left-style': 'solid', + 'border-left-width': '1px', + 'border-right-color': 'rgb(118, 118, 118)', + 'border-right-style': 'solid', + 'border-right-width': '1px', + 'border-top-color': 'rgb(118, 118, 118)', + 'border-top-style': 'solid', + 'border-top-width': '1px', + cursor: 'text', + display: 'inline-block', + 'font-family': 'monospace', + 'font-size': '13.3333px', + height: '31px', + 'inline-size': '176px', + 'overflow-wrap': 'break-word', + 'overflow-x': 'auto', + 'overflow-y': 'auto', + 'padding-block-end': '2px', + 'padding-block-start': '2px', + 'padding-bottom': '2px', + 'padding-inline-end': '2px', + 'padding-inline-start': '2px', + 'padding-left': '2px', + 'padding-right': '2px', + 'padding-top': '2px', + 'perspective-origin': '91px 18.5px', + resize: 'both', + 'transform-origin': '91px 18.5px', + 'white-space': 'pre-wrap', + width: '176px' + }, + SCRIPT: { + display: 'none', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + IMG: { + 'block-size': '0px', + height: '0px', + 'inline-size': '0px', + width: '0px' + }, + LINK: { + display: 'none', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + STYLE: { + display: 'none', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + LABEL: { + cursor: 'default' + }, + SLOT: { + display: 'contents', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + SVG: {}, + CIRCLE: {}, + ELLIPSE: {}, + LINE: {}, + PATH: {}, + POLYGON: {}, + POLYLINE: {}, + RECT: {}, + STOP: {}, + USE: {}, + META: { + display: 'none', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + BLOCKQUOTE: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1432px', + 'margin-block-end': '13px', + 'margin-block-start': '13px', + 'margin-bottom': '13px', + 'margin-inline-end': '40px', + 'margin-inline-start': '40px', + 'margin-left': '40px', + 'margin-right': '40px', + 'margin-top': '13px', + 'perspective-origin': '716px 0px', + 'transform-origin': '716px 0px', + width: '1432px' + }, + BR: {}, + BUTTON: { + 'align-items': 'flex-start', + appearance: 'auto', + 'background-color': 'rgb(239, 239, 239)', + 'block-size': '6px', + 'border-block-end-color': 'rgb(118, 118, 118)', + 'border-block-end-style': 'outset', + 'border-block-end-width': '2px', + 'border-block-start-color': 'rgb(118, 118, 118)', + 'border-block-start-style': 'outset', + 'border-block-start-width': '2px', + 'border-bottom-color': 'rgb(118, 118, 118)', + 'border-bottom-style': 'outset', + 'border-bottom-width': '2px', + 'border-inline-end-color': 'rgb(118, 118, 118)', + 'border-inline-end-style': 'outset', + 'border-inline-end-width': '2px', + 'border-inline-start-color': 'rgb(118, 118, 118)', + 'border-inline-start-style': 'outset', + 'border-inline-start-width': '2px', + 'border-left-color': 'rgb(118, 118, 118)', + 'border-left-style': 'outset', + 'border-left-width': '2px', + 'border-right-color': 'rgb(118, 118, 118)', + 'border-right-style': 'outset', + 'border-right-width': '2px', + 'border-top-color': 'rgb(118, 118, 118)', + 'border-top-style': 'outset', + 'border-top-width': '2px', + 'box-sizing': 'border-box', + cursor: 'default', + display: 'inline-block', + 'font-size': '13.3333px', + height: '6px', + 'inline-size': '16px', + 'padding-block-end': '1px', + 'padding-block-start': '1px', + 'padding-bottom': '1px', + 'padding-inline-end': '6px', + 'padding-inline-start': '6px', + 'padding-left': '6px', + 'padding-right': '6px', + 'padding-top': '1px', + 'perspective-origin': '8px 3px', + 'text-align': 'center', + 'transform-origin': '8px 3px', + width: '16px' + }, + CANVAS: { + 'block-size': '150px', + height: '150px', + 'inline-size': '300px', + 'perspective-origin': '150px 75px', + 'transform-origin': '150px 75px', + width: '300px' + }, + CAPTION: { + 'block-size': '0px', + display: 'table-caption', + height: '0px', + 'inline-size': '0px', + 'text-align': '-webkit-center', + width: '0px' + }, + CITE: { + 'font-style': 'italic' + }, + CODE: { + 'font-family': 'monospace', + 'font-size': '10.5625px' + }, + COL: { + 'block-size': '0px', + display: 'table-column', + height: '0px', + 'inline-size': '0px', + width: '0px' + }, + COLGROUP: { + 'block-size': '0px', + display: 'table-column-group', + height: '0px', + 'inline-size': '0px', + width: '0px' + }, + DATA: {}, + DATALIST: { + display: 'none', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + DD: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1472px', + 'margin-inline-start': '40px', + 'margin-left': '40px', + 'perspective-origin': '736px 0px', + 'transform-origin': '736px 0px', + width: '1472px' + }, + DEL: { + 'text-decoration': 'line-through solid rgb(0, 0, 0)', + 'text-decoration-line': 'line-through', + '-webkit-text-decorations-in-effect': 'line-through' + }, + DETAILS: { + 'block-size': '15px', + display: 'block', + height: '15px', + 'inline-size': '1000px', + 'perspective-origin': '756px 7.5px', + 'transform-origin': '756px 7.5px', + width: '1000px' + }, + DFN: { + 'font-style': 'italic' + }, + DIALOG: { + 'background-color': 'rgb(255, 255, 255)', + 'block-size': 'fit-content', + 'border-block-end-style': 'solid', + 'border-block-end-width': '1.5px', + 'border-block-start-style': 'solid', + 'border-block-start-width': '1.5px', + 'border-bottom-style': 'solid', + 'border-bottom-width': '1.5px', + 'border-inline-end-style': 'solid', + 'border-inline-end-width': '1.5px', + 'border-inline-start-style': 'solid', + 'border-inline-start-width': '1.5px', + 'border-left-style': 'solid', + 'border-left-width': '1.5px', + 'border-right-style': 'solid', + 'border-right-width': '1.5px', + 'border-top-style': 'solid', + 'border-top-width': '1.5px', + display: 'none', + height: 'fit-content', + 'inline-size': 'fit-content', + 'inset-inline-end': '0px', + 'inset-inline-start': '0px', + left: '0px', + 'margin-block-end': 'auto', + 'margin-block-start': 'auto', + 'margin-bottom': 'auto', + 'margin-inline-end': 'auto', + 'margin-inline-start': 'auto', + 'margin-left': 'auto', + 'margin-right': 'auto', + 'margin-top': 'auto', + 'padding-block-end': '13px', + 'padding-block-start': '13px', + 'padding-bottom': '13px', + 'padding-inline-end': '13px', + 'padding-inline-start': '13px', + 'padding-left': '13px', + 'padding-right': '13px', + 'padding-top': '13px', + 'perspective-origin': '50% 50%', + position: 'absolute', + right: '0px', + 'transform-origin': '50% 50%', + width: 'fit-content' + }, + DIV: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + DL: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'margin-block-end': '13px', + 'margin-block-start': '13px', + 'margin-bottom': '13px', + 'margin-top': '13px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + DT: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + EM: { + 'font-style': 'italic' + }, + EMBED: { + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + FIELDSET: { + 'block-size': '0px', + 'border-block-end-color': 'rgb(192, 192, 192)', + 'border-block-end-style': 'groove', + 'border-block-end-width': '2px', + 'border-block-start-color': 'rgb(192, 192, 192)', + 'border-block-start-style': 'groove', + 'border-block-start-width': '2px', + 'border-bottom-color': 'rgb(192, 192, 192)', + 'border-bottom-style': 'groove', + 'border-bottom-width': '2px', + 'border-inline-end-color': 'rgb(192, 192, 192)', + 'border-inline-end-style': 'groove', + 'border-inline-end-width': '2px', + 'border-inline-start-color': 'rgb(192, 192, 192)', + 'border-inline-start-style': 'groove', + 'border-inline-start-width': '2px', + 'border-left-color': 'rgb(192, 192, 192)', + 'border-left-style': 'groove', + 'border-left-width': '2px', + 'border-right-color': 'rgb(192, 192, 192)', + 'border-right-style': 'groove', + 'border-right-width': '2px', + 'border-top-color': 'rgb(192, 192, 192)', + 'border-top-style': 'groove', + 'border-top-width': '2px', + display: 'block', + height: '0px', + 'inline-size': '1484.5px', + 'margin-inline-end': '2px', + 'margin-inline-start': '2px', + 'margin-left': '2px', + 'margin-right': '2px', + 'min-inline-size': 'min-content', + 'min-width': 'min-content', + 'padding-block-end': '8.125px', + 'padding-block-start': '4.55px', + 'padding-bottom': '8.125px', + 'padding-inline-end': '9.75px', + 'padding-inline-start': '9.75px', + 'padding-left': '9.75px', + 'padding-right': '9.75px', + 'padding-top': '4.55px', + 'perspective-origin': '754px 8.33594px', + 'transform-origin': '754px 8.33594px', + width: '1484.5px' + }, + FIGCAPTION: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + FIGURE: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1432px', + 'margin-block-end': '13px', + 'margin-block-start': '13px', + 'margin-bottom': '13px', + 'margin-inline-end': '40px', + 'margin-inline-start': '40px', + 'margin-left': '40px', + 'margin-right': '40px', + 'margin-top': '13px', + 'perspective-origin': '716px 0px', + 'transform-origin': '716px 0px', + width: '1432px' + }, + FOOTER: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + H1: { + 'block-size': '0px', + display: 'block', + 'font-size': '26px', + 'font-weight': '700', + height: '0px', + 'inline-size': '1000px', + 'margin-block-end': '17.42px', + 'margin-block-start': '17.42px', + 'margin-bottom': '17.42px', + 'margin-top': '17.42px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + H2: { + 'block-size': '0px', + display: 'block', + 'font-size': '19.5px', + 'font-weight': '700', + height: '0px', + 'inline-size': '1000px', + 'margin-block-end': '16.185px', + 'margin-block-start': '16.185px', + 'margin-bottom': '16.185px', + 'margin-top': '16.185px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + H3: { + 'block-size': '0px', + display: 'block', + 'font-size': '15.21px', + 'font-weight': '700', + height: '0px', + 'inline-size': '1000px', + 'margin-block-end': '15.21px', + 'margin-block-start': '15.21px', + 'margin-bottom': '15.21px', + 'margin-top': '15.21px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + H4: { + 'block-size': '0px', + display: 'block', + 'font-weight': '700', + height: '0px', + 'inline-size': '1000px', + 'margin-block-end': '17.29px', + 'margin-block-start': '17.29px', + 'margin-bottom': '17.29px', + 'margin-top': '17.29px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + H5: { + 'block-size': '0px', + display: 'block', + 'font-size': '10.79px', + 'font-weight': '700', + height: '0px', + 'inline-size': '1000px', + 'margin-block-end': '18.0193px', + 'margin-block-start': '18.0193px', + 'margin-bottom': '18.0193px', + 'margin-top': '18.0193px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + H6: { + 'block-size': '0px', + display: 'block', + 'font-size': '8.71px', + 'font-weight': '700', + height: '0px', + 'inline-size': '1000px', + 'margin-block-end': '20.2943px', + 'margin-block-start': '20.2943px', + 'margin-bottom': '20.2943px', + 'margin-top': '20.2943px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + HEAD: { + display: 'none', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + HEADER: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + HGROUP: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + HR: { + 'block-size': '0px', + 'border-block-end-style': 'inset', + 'border-block-end-width': '1px', + 'border-block-start-style': 'inset', + 'border-block-start-width': '1px', + 'border-bottom-style': 'inset', + 'border-bottom-width': '1px', + 'border-inline-end-style': 'inset', + 'border-inline-end-width': '1px', + 'border-inline-start-style': 'inset', + 'border-inline-start-width': '1px', + 'border-left-style': 'inset', + 'border-left-width': '1px', + 'border-right-style': 'inset', + 'border-right-width': '1px', + 'border-top-style': 'inset', + 'border-top-width': '1px', + display: 'block', + height: '0px', + 'inline-size': '1510px', + 'margin-block-end': '6.5px', + 'margin-block-start': '6.5px', + 'margin-bottom': '6.5px', + 'margin-top': '6.5px', + 'overflow-x': 'hidden', + 'overflow-y': 'hidden', + 'perspective-origin': '756px 1px', + 'transform-origin': '756px 1px', + 'unicode-bidi': 'isolate', + width: '1510px' + }, + HTML: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + I: { + 'font-style': 'italic' + }, + IFRAME: { + 'block-size': '150px', + 'border-block-end-style': 'inset', + 'border-block-end-width': '2px', + 'border-block-start-style': 'inset', + 'border-block-start-width': '2px', + 'border-bottom-style': 'inset', + 'border-bottom-width': '2px', + 'border-inline-end-style': 'inset', + 'border-inline-end-width': '2px', + 'border-inline-start-style': 'inset', + 'border-inline-start-width': '2px', + 'border-left-style': 'inset', + 'border-left-width': '2px', + 'border-right-style': 'inset', + 'border-right-width': '2px', + 'border-top-style': 'inset', + 'border-top-width': '2px', + height: '150px', + 'inline-size': '300px', + 'perspective-origin': '152px 77px', + 'transform-origin': '152px 77px', + width: '300px' + }, + INS: { + 'text-decoration': 'underline solid rgb(0, 0, 0)', + 'text-decoration-line': 'underline', + '-webkit-text-decorations-in-effect': 'underline' + }, + KBD: { + 'font-family': 'monospace', + 'font-size': '10.5625px' + }, + LEGEND: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1508px', + 'padding-inline-end': '2px', + 'padding-inline-start': '2px', + 'padding-left': '2px', + 'padding-right': '2px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1508px' + }, + LI: { + 'block-size': '15px', + display: 'list-item', + height: '15px', + 'inline-size': '1000px', + 'perspective-origin': '756px 7.5px', + 'text-align': 'left', + 'transform-origin': '756px 7.5px', + width: '1000px' + }, + MAIN: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + MAP: {}, + MARK: { + 'background-color': 'rgb(255, 255, 0)' + }, + MATH: {}, + MENU: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1472px', + 'margin-block-end': '13px', + 'margin-block-start': '13px', + 'margin-bottom': '13px', + 'margin-top': '13px', + 'padding-inline-start': '40px', + 'padding-left': '40px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1472px' + }, + MENUITEM: {}, + METER: { + appearance: 'auto', + 'block-size': '13px', + 'box-sizing': 'border-box', + display: 'inline-block', + height: '13px', + 'inline-size': '65px', + 'perspective-origin': '32.5px 6.5px', + 'transform-origin': '32.5px 6.5px', + 'vertical-align': '-2.6px', + width: '65px' + }, + NAV: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + NOSCRIPT: { + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + OBJECT: { + 'block-size': '150px', + height: '150px', + 'inline-size': '300px', + 'perspective-origin': '150px 75px', + 'transform-origin': '150px 75px', + width: '300px' + }, + OL: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1472px', + 'list-style-type': 'decimal', + 'margin-block-end': '13px', + 'margin-block-start': '13px', + 'margin-bottom': '13px', + 'margin-top': '13px', + 'padding-inline-start': '40px', + 'padding-left': '40px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1472px' + }, + OPTGROUP: { + 'block-size': '16.5938px', + display: 'block', + 'font-weight': '700', + height: '16.5938px', + 'inline-size': '1000px', + 'perspective-origin': '756px 8.29688px', + 'transform-origin': '756px 8.29688px', + width: '1000px' + }, + OPTION: { + 'block-size': '15.5938px', + display: 'block', + height: '15.5938px', + 'inline-size': '1508px', + 'min-block-size': '15.6px', + 'min-height': '15.6px', + 'padding-block-end': '1px', + 'padding-bottom': '1px', + 'padding-inline-end': '2px', + 'padding-inline-start': '2px', + 'padding-left': '2px', + 'padding-right': '2px', + 'perspective-origin': '756px 8.29688px', + 'transform-origin': '756px 8.29688px', + 'white-space': 'nowrap', + width: '1508px' + }, + OUTPUT: { + 'unicode-bidi': 'isolate' + }, + P: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'margin-block-end': '13px', + 'margin-block-start': '13px', + 'margin-bottom': '13px', + 'margin-top': '13px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + PARAM: { + display: 'none', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + PICTURE: {}, + PRE: { + 'block-size': '0px', + display: 'block', + 'font-family': 'monospace', + 'font-size': '10.5625px', + height: '0px', + 'inline-size': '1000px', + 'margin-block-end': '10.5625px', + 'margin-block-start': '10.5625px', + 'margin-bottom': '10.5625px', + 'margin-top': '10.5625px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + 'white-space': 'pre', + width: '1000px' + }, + PROGRESS: { + appearance: 'auto', + 'block-size': '13px', + 'box-sizing': 'border-box', + display: 'inline-block', + height: '13px', + 'inline-size': '130px', + 'perspective-origin': '65px 6.5px', + 'transform-origin': '65px 6.5px', + 'vertical-align': '-2.6px', + width: '130px' + }, + Q: {}, + RB: {}, + RP: { + display: 'none', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + RT: {}, + RTC: {}, + RUBY: {}, + S: { + 'text-decoration': 'line-through solid rgb(0, 0, 0)', + 'text-decoration-line': 'line-through', + '-webkit-text-decorations-in-effect': 'line-through' + }, + SAMP: { + 'font-family': 'monospace', + 'font-size': '10.5625px' + }, + SECTION: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + SELECT: { + 'align-items': 'center', + appearance: 'auto', + 'background-color': 'rgb(255, 255, 255)', + 'block-size': '19px', + 'border-block-end-color': 'rgb(118, 118, 118)', + 'border-block-end-style': 'solid', + 'border-block-end-width': '1px', + 'border-block-start-color': 'rgb(118, 118, 118)', + 'border-block-start-style': 'solid', + 'border-block-start-width': '1px', + 'border-bottom-color': 'rgb(118, 118, 118)', + 'border-bottom-style': 'solid', + 'border-bottom-width': '1px', + 'border-inline-end-color': 'rgb(118, 118, 118)', + 'border-inline-end-style': 'solid', + 'border-inline-end-width': '1px', + 'border-inline-start-color': 'rgb(118, 118, 118)', + 'border-inline-start-style': 'solid', + 'border-inline-start-width': '1px', + 'border-left-color': 'rgb(118, 118, 118)', + 'border-left-style': 'solid', + 'border-left-width': '1px', + 'border-right-color': 'rgb(118, 118, 118)', + 'border-right-style': 'solid', + 'border-right-width': '1px', + 'border-top-color': 'rgb(118, 118, 118)', + 'border-top-style': 'solid', + 'border-top-width': '1px', + 'box-sizing': 'border-box', + cursor: 'default', + display: 'inline-block', + 'font-family': 'Arial', + 'font-size': '13.3333px', + height: '19px', + 'inline-size': '22px', + 'perspective-origin': '11px 9.5px', + 'transform-origin': '11px 9.5px', + 'white-space': 'pre', + width: '22px' + }, + SMALL: { + 'font-size': '10.8333px' + }, + SOURCE: {}, + SPAN: {}, + STRONG: { + 'font-weight': '700' + }, + SUB: { + 'font-size': '10.8333px', + 'vertical-align': 'sub' + }, + SUMMARY: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + SUP: { + 'font-size': '10.8333px', + 'vertical-align': 'super' + }, + TABLE: { + 'block-size': '0px', + 'border-block-end-color': 'rgb(128, 128, 128)', + 'border-block-start-color': 'rgb(128, 128, 128)', + 'border-bottom-color': 'rgb(128, 128, 128)', + 'border-inline-end-color': 'rgb(128, 128, 128)', + 'border-inline-start-color': 'rgb(128, 128, 128)', + 'border-left-color': 'rgb(128, 128, 128)', + 'border-right-color': 'rgb(128, 128, 128)', + 'border-top-color': 'rgb(128, 128, 128)', + 'box-sizing': 'border-box', + display: 'table', + height: '0px', + 'inline-size': '0px', + width: '0px', + '-webkit-border-horizontal-spacing': '2px', + '-webkit-border-vertical-spacing': '2px' + }, + TBODY: { + 'block-size': '0px', + display: 'table-row-group', + height: '0px', + 'inline-size': '0px', + 'vertical-align': 'middle', + width: '0px' + }, + TD: { + 'block-size': '0px', + display: 'table-cell', + height: '0px', + 'inline-size': '0px', + width: '0px' + }, + TFOOT: { + 'block-size': '0px', + display: 'table-footer-group', + height: '0px', + 'inline-size': '0px', + 'vertical-align': 'middle', + width: '0px' + }, + TH: { + 'block-size': '0px', + display: 'table-cell', + 'font-weight': '700', + height: '0px', + 'inline-size': '0px', + 'text-align': 'center', + width: '0px' + }, + THEAD: { + 'block-size': '0px', + display: 'table-header-group', + height: '0px', + 'inline-size': '0px', + 'vertical-align': 'middle', + width: '0px' + }, + TIME: {}, + TITLE: { + display: 'none', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + TR: { + 'block-size': '0px', + display: 'table-row', + height: '0px', + 'inline-size': '0px', + width: '0px' + }, + TRACK: {}, + U: { + 'text-decoration': 'underline solid rgb(0, 0, 0)', + 'text-decoration-line': 'underline', + '-webkit-text-decorations-in-effect': 'underline' + }, + UL: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1472px', + 'margin-block-end': '13px', + 'margin-block-start': '13px', + 'margin-bottom': '13px', + 'margin-top': '13px', + 'padding-inline-start': '40px', + 'padding-left': '40px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1472px' + }, + VAR: { + 'font-style': 'italic' + }, + VIDEO: { + 'block-size': '150px', + height: '150px', + 'inline-size': '300px', + 'object-fit': 'contain', + 'perspective-origin': '150px 75px', + 'transform-origin': '150px 75px', + width: '300px' + }, + WBR: {} +}; diff --git a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts new file mode 100644 index 000000000..fe0be1ef1 --- /dev/null +++ b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts @@ -0,0 +1,236 @@ +import IElement from '../../nodes/element/IElement'; +import Attr from '../../nodes/attr/Attr'; +import CSSRule from '../CSSRule'; +import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum'; +import DOMException from '../../exception/DOMException'; +import CSSStyleDeclarationUtility from './CSSStyleDeclarationUtility'; + +/** + * CSS Style Declaration. + */ +export default abstract class AbstractCSSStyleDeclaration { + // Other properties + public readonly parentRule: CSSRule = null; + public _readonly = false; + protected _styles: { [k: string]: string } = {}; + protected _ownerElement: IElement; + + /** + * Constructor. + * + * @param [ownerElement] Computed style element. + */ + constructor(ownerElement: IElement = null) { + this._ownerElement = ownerElement; + } + + /** + * Returns length. + * + * @returns Length. + */ + public get length(): number { + if (this._ownerElement) { + if ( + this._ownerElement['_attributes']['style'] && + this._ownerElement['_attributes']['style'].value + ) { + return Object.keys( + CSSStyleDeclarationUtility.styleStringToObject( + this._ownerElement['_attributes']['style'].value + ) + ).length; + } + return 0; + } + + return Object.keys(this._styles).length; + } + + /** + * Returns the style decleration as a CSS text. + * + * @returns CSS text. + */ + public get cssText(): string { + if (this._ownerElement) { + if ( + this._ownerElement['_attributes']['style'] && + this._ownerElement['_attributes']['style'].value + ) { + return CSSStyleDeclarationUtility.styleObjectToString( + CSSStyleDeclarationUtility.styleStringToObject( + this._ownerElement['_attributes']['style'].value + ) + ); + } + return ''; + } + + return CSSStyleDeclarationUtility.styleObjectToString(this._styles); + } + + /** + * Sets CSS text. + * + * @param cssText CSS text. + */ + public set cssText(cssText: string) { + if (this._readonly) { + throw new DOMException( + `Failed to execute 'cssText' on 'CSSStyleDeclaration': These styles are computed, and the properties are therefore read-only.`, + DOMExceptionNameEnum.domException + ); + } + + if (this._ownerElement) { + const parsed = CSSStyleDeclarationUtility.styleObjectToString( + CSSStyleDeclarationUtility.styleStringToObject(cssText) + ); + if (!parsed) { + delete this._ownerElement['_attributes']['style']; + } else { + if (!this._ownerElement['_attributes']['style']) { + Attr._ownerDocument = this._ownerElement.ownerDocument; + this._ownerElement['_attributes']['style'] = new Attr(); + this._ownerElement['_attributes']['style'].name = 'style'; + } + + this._ownerElement['_attributes']['style'].value = parsed; + } + } else { + this._styles = CSSStyleDeclarationUtility.styleStringToObject(cssText); + } + } + + /** + * Returns item. + * + * @param index Index. + * @returns Item. + */ + public item(index: number): string { + if (this._ownerElement) { + if ( + this._ownerElement['_attributes']['style'] && + this._ownerElement['_attributes']['style'].value + ) { + return ( + Object.keys( + CSSStyleDeclarationUtility.styleStringToObject( + this._ownerElement['_attributes']['style'].value + ) + )[index] || '' + ); + } + return ''; + } + return Object.keys(this._styles)[index] || ''; + } + + /** + * Set a property. + * + * @param propertyName 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 { + if (this._readonly) { + throw new DOMException( + `Failed to execute 'setProperty' on 'CSSStyleDeclaration': These styles are computed, and therefore the '${propertyName}' property is read-only.`, + DOMExceptionNameEnum.domException + ); + } + + if (priority !== '' && priority !== undefined && priority !== 'important') { + return; + } + + const important = priority ? ' !important' : ''; + + if (!value) { + this.removeProperty(propertyName); + } else if (this._ownerElement) { + if (!this._ownerElement['_attributes']['style']) { + Attr._ownerDocument = this._ownerElement.ownerDocument; + this._ownerElement['_attributes']['style'] = new Attr(); + this._ownerElement['_attributes']['style'].name = 'style'; + } + + const styleObject = CSSStyleDeclarationUtility.styleStringToObject( + this._ownerElement['_attributes']['style'].value + ); + + styleObject[propertyName] = value + important; + + this._ownerElement['_attributes']['style'].value = + CSSStyleDeclarationUtility.styleObjectToString(styleObject); + } else { + this._styles[propertyName] = value + important; + } + } + + /** + * Removes a property. + * + * @param propertyName 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 { + if (this._readonly) { + throw new DOMException( + `Failed to execute 'removeProperty' on 'CSSStyleDeclaration': These styles are computed, and therefore the '${propertyName}' property is read-only.`, + DOMExceptionNameEnum.domException + ); + } + + if (this._ownerElement) { + if (this._ownerElement['_attributes']['style']) { + const styleObject = CSSStyleDeclarationUtility.styleStringToObject( + this._ownerElement['_attributes']['style'].value + ); + + delete styleObject[propertyName]; + + const styleString = CSSStyleDeclarationUtility.styleObjectToString(styleObject); + + if (styleString) { + this._ownerElement['_attributes']['style'].value = styleString; + } else { + delete this._ownerElement['_attributes']['style']; + } + } + } else { + delete this._styles[propertyName]; + } + } + + /** + * Returns a property. + * + * @param propertyName Property name in kebab case. + * @returns Property value. + */ + public getPropertyValue(propertyName: string): string { + if (this._ownerElement) { + if ( + !this._ownerElement['_attributes']['style'] || + !this._ownerElement['_attributes']['style'].value + ) { + return ''; + } + const value = CSSStyleDeclarationUtility.styleStringToObject( + this._ownerElement['_attributes']['style'].value + )[propertyName]; + return value ? value.replace(' !important', '') : ''; + } + + return this._styles[propertyName] ? this._styles[propertyName].replace(' !important', '') : ''; + } +} diff --git a/packages/happy-dom/src/css/declaration/CSSStyleDeclaration.ts b/packages/happy-dom/src/css/declaration/CSSStyleDeclaration.ts new file mode 100644 index 000000000..7c48e55e6 --- /dev/null +++ b/packages/happy-dom/src/css/declaration/CSSStyleDeclaration.ts @@ -0,0 +1,4444 @@ +import AbstractCSSStyleDeclaration from './AbstractCSSStyleDeclaration'; + +/* eslint-disable jsdoc/require-jsdoc */ + +/** + * CSS Style Declaration. + */ +export default class CSSStyleDeclaration extends AbstractCSSStyleDeclaration { + /** + * Index properties + */ + + public get 0(): string { + return this.item(0) || undefined; + } + + public get 1(): string { + return this.item(1) || undefined; + } + + public get 2(): string { + return this.item(2) || undefined; + } + + public get 3(): string { + return this.item(3) || undefined; + } + + public get 4(): string { + return this.item(4) || undefined; + } + + public get 5(): string { + return this.item(5) || undefined; + } + + public get 6(): string { + return this.item(6) || undefined; + } + + public get 7(): string { + return this.item(7) || undefined; + } + + public get 8(): string { + return this.item(8) || undefined; + } + + public get 9(): string { + return this.item(9) || undefined; + } + + public get 10(): string { + return this.item(10) || undefined; + } + + public get 11(): string { + return this.item(11) || undefined; + } + + public get 12(): string { + return this.item(12) || undefined; + } + + public get 13(): string { + return this.item(13) || undefined; + } + + public get 14(): string { + return this.item(14) || undefined; + } + + public get 15(): string { + return this.item(15) || undefined; + } + + public get 16(): string { + return this.item(16) || undefined; + } + + public get 17(): string { + return this.item(17) || undefined; + } + + public get 18(): string { + return this.item(18) || undefined; + } + + public get 19(): string { + return this.item(19) || undefined; + } + + public get 20(): string { + return this.item(20) || undefined; + } + + public get 21(): string { + return this.item(21) || undefined; + } + + public get 22(): string { + return this.item(22) || undefined; + } + + public get 23(): string { + return this.item(23) || undefined; + } + + public get 24(): string { + return this.item(24) || undefined; + } + + public get 25(): string { + return this.item(25) || undefined; + } + + public get 26(): string { + return this.item(26) || undefined; + } + + public get 27(): string { + return this.item(27) || undefined; + } + + public get 28(): string { + return this.item(28) || undefined; + } + + public get 29(): string { + return this.item(29) || undefined; + } + + public get 30(): string { + return this.item(30) || undefined; + } + + public get 31(): string { + return this.item(31) || undefined; + } + + public get 32(): string { + return this.item(32) || undefined; + } + + public get 33(): string { + return this.item(33) || undefined; + } + + public get 34(): string { + return this.item(34) || undefined; + } + + public get 35(): string { + return this.item(35) || undefined; + } + + public get 36(): string { + return this.item(36) || undefined; + } + + public get 37(): string { + return this.item(37) || undefined; + } + + public get 38(): string { + return this.item(38) || undefined; + } + + public get 39(): string { + return this.item(39) || undefined; + } + + public get 40(): string { + return this.item(40) || undefined; + } + + public get 41(): string { + return this.item(41) || undefined; + } + + public get 42(): string { + return this.item(42) || undefined; + } + + public get 43(): string { + return this.item(43) || undefined; + } + + public get 44(): string { + return this.item(44) || undefined; + } + + public get 45(): string { + return this.item(45) || undefined; + } + + public get 46(): string { + return this.item(46) || undefined; + } + + public get 47(): string { + return this.item(47) || undefined; + } + + public get 48(): string { + return this.item(48) || undefined; + } + + public get 49(): string { + return this.item(49) || undefined; + } + + public get 50(): string { + return this.item(50) || undefined; + } + + public get 51(): string { + return this.item(51) || undefined; + } + + public get 52(): string { + return this.item(52) || undefined; + } + + public get 53(): string { + return this.item(53) || undefined; + } + + public get 54(): string { + return this.item(54) || undefined; + } + + public get 55(): string { + return this.item(55) || undefined; + } + + public get 56(): string { + return this.item(56) || undefined; + } + + public get 57(): string { + return this.item(57) || undefined; + } + + public get 58(): string { + return this.item(58) || undefined; + } + + public get 59(): string { + return this.item(59) || undefined; + } + + public get 60(): string { + return this.item(60) || undefined; + } + + public get 61(): string { + return this.item(61) || undefined; + } + + public get 62(): string { + return this.item(62) || undefined; + } + + public get 63(): string { + return this.item(63) || undefined; + } + + public get 64(): string { + return this.item(64) || undefined; + } + + public get 65(): string { + return this.item(65) || undefined; + } + + public get 66(): string { + return this.item(66) || undefined; + } + + public get 67(): string { + return this.item(67) || undefined; + } + + public get 68(): string { + return this.item(68) || undefined; + } + + public get 69(): string { + return this.item(69) || undefined; + } + + public get 70(): string { + return this.item(70) || undefined; + } + + public get 71(): string { + return this.item(71) || undefined; + } + + public get 72(): string { + return this.item(72) || undefined; + } + + public get 73(): string { + return this.item(73) || undefined; + } + + public get 74(): string { + return this.item(74) || undefined; + } + + public get 75(): string { + return this.item(75) || undefined; + } + + public get 76(): string { + return this.item(76) || undefined; + } + + public get 77(): string { + return this.item(77) || undefined; + } + + public get 78(): string { + return this.item(78) || undefined; + } + + public get 79(): string { + return this.item(79) || undefined; + } + + public get 80(): string { + return this.item(80) || undefined; + } + + public get 81(): string { + return this.item(81) || undefined; + } + + public get 82(): string { + return this.item(82) || undefined; + } + + public get 83(): string { + return this.item(83) || undefined; + } + + public get 84(): string { + return this.item(84) || undefined; + } + + public get 85(): string { + return this.item(85) || undefined; + } + + public get 86(): string { + return this.item(86) || undefined; + } + + public get 87(): string { + return this.item(87) || undefined; + } + + public get 88(): string { + return this.item(88) || undefined; + } + + public get 89(): string { + return this.item(89) || undefined; + } + + public get 90(): string { + return this.item(90) || undefined; + } + + public get 91(): string { + return this.item(91) || undefined; + } + + public get 92(): string { + return this.item(92) || undefined; + } + + public get 93(): string { + return this.item(93) || undefined; + } + + public get 94(): string { + return this.item(94) || undefined; + } + + public get 95(): string { + return this.item(95) || undefined; + } + + public get 96(): string { + return this.item(96) || undefined; + } + + public get 97(): string { + return this.item(97) || undefined; + } + + public get 98(): string { + return this.item(98) || undefined; + } + + public get 99(): string { + return this.item(99) || undefined; + } + + public get 100(): string { + return this.item(100) || undefined; + } + + public get 101(): string { + return this.item(101) || undefined; + } + + public get 102(): string { + return this.item(102) || undefined; + } + + public get 103(): string { + return this.item(103) || undefined; + } + + public get 104(): string { + return this.item(104) || undefined; + } + + public get 105(): string { + return this.item(105) || undefined; + } + + public get 106(): string { + return this.item(106) || undefined; + } + + public get 107(): string { + return this.item(107) || undefined; + } + + public get 108(): string { + return this.item(108) || undefined; + } + + public get 109(): string { + return this.item(109) || undefined; + } + + public get 110(): string { + return this.item(110) || undefined; + } + + public get 111(): string { + return this.item(111) || undefined; + } + + public get 112(): string { + return this.item(112) || undefined; + } + + public get 113(): string { + return this.item(113) || undefined; + } + + public get 114(): string { + return this.item(114) || undefined; + } + + public get 115(): string { + return this.item(115) || undefined; + } + + public get 116(): string { + return this.item(116) || undefined; + } + + public get 117(): string { + return this.item(117) || undefined; + } + + public get 118(): string { + return this.item(118) || undefined; + } + + public get 119(): string { + return this.item(119) || undefined; + } + + public get 120(): string { + return this.item(120) || undefined; + } + + public get 121(): string { + return this.item(121) || undefined; + } + + public get 122(): string { + return this.item(122) || undefined; + } + + public get 123(): string { + return this.item(123) || undefined; + } + + public get 124(): string { + return this.item(124) || undefined; + } + + public get 125(): string { + return this.item(125) || undefined; + } + + public get 126(): string { + return this.item(126) || undefined; + } + + public get 127(): string { + return this.item(127) || undefined; + } + + public get 128(): string { + return this.item(128) || undefined; + } + + public get 129(): string { + return this.item(129) || undefined; + } + + public get 130(): string { + return this.item(130) || undefined; + } + + public get 131(): string { + return this.item(131) || undefined; + } + + public get 132(): string { + return this.item(132) || undefined; + } + + public get 133(): string { + return this.item(133) || undefined; + } + + public get 134(): string { + return this.item(134) || undefined; + } + + public get 135(): string { + return this.item(135) || undefined; + } + + public get 136(): string { + return this.item(136) || undefined; + } + + public get 137(): string { + return this.item(137) || undefined; + } + + public get 138(): string { + return this.item(138) || undefined; + } + + public get 139(): string { + return this.item(139) || undefined; + } + + public get 140(): string { + return this.item(140) || undefined; + } + + public get 141(): string { + return this.item(141) || undefined; + } + + public get 142(): string { + return this.item(142) || undefined; + } + + public get 143(): string { + return this.item(143) || undefined; + } + + public get 144(): string { + return this.item(144) || undefined; + } + + public get 145(): string { + return this.item(145) || undefined; + } + + public get 146(): string { + return this.item(146) || undefined; + } + + public get 147(): string { + return this.item(147) || undefined; + } + + public get 148(): string { + return this.item(148) || undefined; + } + + public get 149(): string { + return this.item(149) || undefined; + } + + public get 150(): string { + return this.item(150) || undefined; + } + + public get 151(): string { + return this.item(151) || undefined; + } + + public get 152(): string { + return this.item(152) || undefined; + } + + public get 153(): string { + return this.item(153) || undefined; + } + + public get 154(): string { + return this.item(154) || undefined; + } + + public get 155(): string { + return this.item(155) || undefined; + } + + public get 156(): string { + return this.item(156) || undefined; + } + + public get 157(): string { + return this.item(157) || undefined; + } + + public get 158(): string { + return this.item(158) || undefined; + } + + public get 159(): string { + return this.item(159) || undefined; + } + + public get 160(): string { + return this.item(160) || undefined; + } + + public get 161(): string { + return this.item(161) || undefined; + } + + public get 162(): string { + return this.item(162) || undefined; + } + + public get 163(): string { + return this.item(163) || undefined; + } + + public get 164(): string { + return this.item(164) || undefined; + } + + public get 165(): string { + return this.item(165) || undefined; + } + + public get 166(): string { + return this.item(166) || undefined; + } + + public get 167(): string { + return this.item(167) || undefined; + } + + public get 168(): string { + return this.item(168) || undefined; + } + + public get 169(): string { + return this.item(169) || undefined; + } + + public get 170(): string { + return this.item(170) || undefined; + } + + public get 171(): string { + return this.item(171) || undefined; + } + + public get 172(): string { + return this.item(172) || undefined; + } + + public get 173(): string { + return this.item(173) || undefined; + } + + public get 174(): string { + return this.item(174) || undefined; + } + + public get 175(): string { + return this.item(175) || undefined; + } + + public get 176(): string { + return this.item(176) || undefined; + } + + public get 177(): string { + return this.item(177) || undefined; + } + + public get 178(): string { + return this.item(178) || undefined; + } + + public get 179(): string { + return this.item(179) || undefined; + } + + public get 180(): string { + return this.item(180) || undefined; + } + + public get 181(): string { + return this.item(181) || undefined; + } + + public get 182(): string { + return this.item(182) || undefined; + } + + public get 183(): string { + return this.item(183) || undefined; + } + + public get 184(): string { + return this.item(184) || undefined; + } + + public get 185(): string { + return this.item(185) || undefined; + } + + public get 186(): string { + return this.item(186) || undefined; + } + + public get 187(): string { + return this.item(187) || undefined; + } + + public get 188(): string { + return this.item(188) || undefined; + } + + public get 189(): string { + return this.item(189) || undefined; + } + + public get 190(): string { + return this.item(190) || undefined; + } + + public get 191(): string { + return this.item(191) || undefined; + } + + public get 192(): string { + return this.item(192) || undefined; + } + + public get 193(): string { + return this.item(193) || undefined; + } + + public get 194(): string { + return this.item(194) || undefined; + } + + public get 195(): string { + return this.item(195) || undefined; + } + + public get 196(): string { + return this.item(196) || undefined; + } + + public get 197(): string { + return this.item(197) || undefined; + } + + public get 198(): string { + return this.item(198) || undefined; + } + + public get 199(): string { + return this.item(199) || undefined; + } + + public get 200(): string { + return this.item(200) || undefined; + } + + public get 201(): string { + return this.item(201) || undefined; + } + + public get 202(): string { + return this.item(202) || undefined; + } + + public get 203(): string { + return this.item(203) || undefined; + } + + public get 204(): string { + return this.item(204) || undefined; + } + + public get 205(): string { + return this.item(205) || undefined; + } + + public get 206(): string { + return this.item(206) || undefined; + } + + public get 207(): string { + return this.item(207) || undefined; + } + + public get 208(): string { + return this.item(208) || undefined; + } + + public get 209(): string { + return this.item(209) || undefined; + } + + public get 210(): string { + return this.item(210) || undefined; + } + + public get 211(): string { + return this.item(211) || undefined; + } + + public get 212(): string { + return this.item(212) || undefined; + } + + public get 213(): string { + return this.item(213) || undefined; + } + + public get 214(): string { + return this.item(214) || undefined; + } + + public get 215(): string { + return this.item(215) || undefined; + } + + public get 216(): string { + return this.item(216) || undefined; + } + + public get 217(): string { + return this.item(217) || undefined; + } + + public get 218(): string { + return this.item(218) || undefined; + } + + public get 219(): string { + return this.item(219) || undefined; + } + + public get 220(): string { + return this.item(220) || undefined; + } + + public get 221(): string { + return this.item(221) || undefined; + } + + public get 222(): string { + return this.item(222) || undefined; + } + + public get 223(): string { + return this.item(223) || undefined; + } + + public get 224(): string { + return this.item(224) || undefined; + } + + public get 225(): string { + return this.item(225) || undefined; + } + + public get 226(): string { + return this.item(226) || undefined; + } + + public get 227(): string { + return this.item(227) || undefined; + } + + public get 228(): string { + return this.item(228) || undefined; + } + + public get 229(): string { + return this.item(229) || undefined; + } + + public get 230(): string { + return this.item(230) || undefined; + } + + public get 231(): string { + return this.item(231) || undefined; + } + + public get 232(): string { + return this.item(232) || undefined; + } + + public get 233(): string { + return this.item(233) || undefined; + } + + public get 234(): string { + return this.item(234) || undefined; + } + + public get 235(): string { + return this.item(235) || undefined; + } + + public get 236(): string { + return this.item(236) || undefined; + } + + public get 237(): string { + return this.item(237) || undefined; + } + + public get 238(): string { + return this.item(238) || undefined; + } + + public get 239(): string { + return this.item(239) || undefined; + } + + public get 240(): string { + return this.item(240) || undefined; + } + + public get 241(): string { + return this.item(241) || undefined; + } + + public get 242(): string { + return this.item(242) || undefined; + } + + public get 243(): string { + return this.item(243) || undefined; + } + + public get 244(): string { + return this.item(244) || undefined; + } + + public get 245(): string { + return this.item(245) || undefined; + } + + public get 246(): string { + return this.item(246) || undefined; + } + + public get 247(): string { + return this.item(247) || undefined; + } + + public get 248(): string { + return this.item(248) || undefined; + } + + public get 249(): string { + return this.item(249) || undefined; + } + + public get 250(): string { + return this.item(250) || undefined; + } + + public get 251(): string { + return this.item(251) || undefined; + } + + public get 252(): string { + return this.item(252) || undefined; + } + + public get 253(): string { + return this.item(253) || undefined; + } + + public get 254(): string { + return this.item(254) || undefined; + } + + public get 255(): string { + return this.item(255) || undefined; + } + + public get 256(): string { + return this.item(256) || undefined; + } + + public get 257(): string { + return this.item(257) || undefined; + } + + public get 258(): string { + return this.item(258) || undefined; + } + + public get 259(): string { + return this.item(259) || undefined; + } + + public get 260(): string { + return this.item(260) || undefined; + } + + public get 261(): string { + return this.item(261) || undefined; + } + + public get 262(): string { + return this.item(262) || undefined; + } + + public get 263(): string { + return this.item(263) || undefined; + } + + public get 264(): string { + return this.item(264) || undefined; + } + + public get 265(): string { + return this.item(265) || undefined; + } + + public get 266(): string { + return this.item(266) || undefined; + } + + public get 267(): string { + return this.item(267) || undefined; + } + + public get 268(): string { + return this.item(268) || undefined; + } + + public get 269(): string { + return this.item(269) || undefined; + } + + public get 270(): string { + return this.item(270) || undefined; + } + + public get 271(): string { + return this.item(271) || undefined; + } + + public get 272(): string { + return this.item(272) || undefined; + } + + public get 273(): string { + return this.item(273) || undefined; + } + + public get 274(): string { + return this.item(274) || undefined; + } + + public get 275(): string { + return this.item(275) || undefined; + } + + public get 276(): string { + return this.item(276) || undefined; + } + + public get 277(): string { + return this.item(277) || undefined; + } + + public get 278(): string { + return this.item(278) || undefined; + } + + public get 279(): string { + return this.item(279) || undefined; + } + + public get 280(): string { + return this.item(280) || undefined; + } + + public get 281(): string { + return this.item(281) || undefined; + } + + public get 282(): string { + return this.item(282) || undefined; + } + + public get 283(): string { + return this.item(283) || undefined; + } + + public get 284(): string { + return this.item(284) || undefined; + } + + public get 285(): string { + return this.item(285) || undefined; + } + + public get 286(): string { + return this.item(286) || undefined; + } + + public get 287(): string { + return this.item(287) || undefined; + } + + public get 288(): string { + return this.item(288) || undefined; + } + + public get 289(): string { + return this.item(289) || undefined; + } + + public get 290(): string { + return this.item(290) || undefined; + } + + public get 291(): string { + return this.item(291) || undefined; + } + + public get 292(): string { + return this.item(292) || undefined; + } + + public get 293(): string { + return this.item(293) || undefined; + } + + public get 294(): string { + return this.item(294) || undefined; + } + + public get 295(): string { + return this.item(295) || undefined; + } + + public get 296(): string { + return this.item(296) || undefined; + } + + public get 297(): string { + return this.item(297) || undefined; + } + + public get 298(): string { + return this.item(298) || undefined; + } + + public get 299(): string { + return this.item(299) || undefined; + } + + public get 300(): string { + return this.item(300) || undefined; + } + + public get 301(): string { + return this.item(301) || undefined; + } + + public get 302(): string { + return this.item(302) || undefined; + } + + public get 303(): string { + return this.item(303) || undefined; + } + + public get 304(): string { + return this.item(304) || undefined; + } + + public get 305(): string { + return this.item(305) || undefined; + } + + public get 306(): string { + return this.item(306) || undefined; + } + + public get 307(): string { + return this.item(307) || undefined; + } + + public get 308(): string { + return this.item(308) || undefined; + } + + public get 309(): string { + return this.item(309) || undefined; + } + + public get 310(): string { + return this.item(310) || undefined; + } + + public get 311(): string { + return this.item(311) || undefined; + } + + public get 312(): string { + return this.item(312) || undefined; + } + + public get 313(): string { + return this.item(313) || undefined; + } + + public get 314(): string { + return this.item(314) || undefined; + } + + public get 315(): string { + return this.item(315) || undefined; + } + + public get 316(): string { + return this.item(316) || undefined; + } + + public get 317(): string { + return this.item(317) || undefined; + } + + public get 318(): string { + return this.item(318) || undefined; + } + + public get 319(): string { + return this.item(319) || undefined; + } + + public get 320(): string { + return this.item(320) || undefined; + } + + public get 321(): string { + return this.item(321) || undefined; + } + + public get 322(): string { + return this.item(322) || undefined; + } + + public get 323(): string { + return this.item(323) || undefined; + } + + public get 324(): string { + return this.item(324) || undefined; + } + + public get 325(): string { + return this.item(325) || undefined; + } + + public get 326(): string { + return this.item(326) || undefined; + } + + public get 327(): string { + return this.item(327) || undefined; + } + + public get 328(): string { + return this.item(328) || undefined; + } + + public get 329(): string { + return this.item(329) || undefined; + } + + public get 330(): string { + return this.item(330) || undefined; + } + + public get 331(): string { + return this.item(331) || undefined; + } + + public get 332(): string { + return this.item(332) || undefined; + } + + public get 333(): string { + return this.item(333) || undefined; + } + + public get 334(): string { + return this.item(334) || undefined; + } + + public get 335(): string { + return this.item(335) || undefined; + } + + public get 336(): string { + return this.item(336) || undefined; + } + + public get 337(): string { + return this.item(337) || undefined; + } + + public get 338(): string { + return this.item(338) || undefined; + } + + public get 339(): string { + return this.item(339) || undefined; + } + + public get 340(): string { + return this.item(340) || undefined; + } + + public get 341(): string { + return this.item(341) || undefined; + } + + public get 342(): string { + return this.item(342) || undefined; + } + + public get 343(): string { + return this.item(343) || undefined; + } + + public get 344(): string { + return this.item(344) || undefined; + } + + public get 345(): string { + return this.item(345) || undefined; + } + + public get 346(): string { + return this.item(346) || undefined; + } + + public get 347(): string { + return this.item(347) || undefined; + } + + public get 348(): string { + return this.item(348) || undefined; + } + + public get 349(): string { + return this.item(349) || undefined; + } + + public get 350(): string { + return this.item(350) || undefined; + } + + public get 351(): string { + return this.item(351) || undefined; + } + + public get 352(): string { + return this.item(352) || undefined; + } + + public get 353(): string { + return this.item(353) || undefined; + } + + public get 354(): string { + return this.item(354) || undefined; + } + + public get 355(): string { + return this.item(355) || undefined; + } + + public get 356(): string { + return this.item(356) || undefined; + } + + public get 357(): string { + return this.item(357) || undefined; + } + + public get 358(): string { + return this.item(358) || undefined; + } + + public get 359(): string { + return this.item(359) || undefined; + } + + public get 360(): string { + return this.item(360) || undefined; + } + + public get 361(): string { + return this.item(361) || undefined; + } + + public get 362(): string { + return this.item(362) || undefined; + } + + public get 363(): string { + return this.item(363) || undefined; + } + + public get 364(): string { + return this.item(364) || undefined; + } + + public get 365(): string { + return this.item(365) || undefined; + } + + public get 366(): string { + return this.item(366) || undefined; + } + + public get 367(): string { + return this.item(367) || undefined; + } + + public get 368(): string { + return this.item(368) || undefined; + } + + /** + * CSS properties + */ + + public get alignContent(): string { + return this.getPropertyValue('align-content'); + } + + public set alignContent(value: string) { + this.setProperty('align-content', value); + } + + public get alignItems(): string { + return this.getPropertyValue('align-items'); + } + + public set alignItems(value: string) { + this.setProperty('align-items', value); + } + + public get alignSelf(): string { + return this.getPropertyValue('align-self'); + } + + public set alignSelf(value: string) { + this.setProperty('align-self', value); + } + + public get alignmentBaseline(): string { + return this.getPropertyValue('alignment-baseline'); + } + + public set alignmentBaseline(value: string) { + this.setProperty('alignment-baseline', value); + } + + public get all(): string { + return this.getPropertyValue('all'); + } + + public set all(value: string) { + this.setProperty('all', value); + } + + public get animation(): string { + return this.getPropertyValue('animation'); + } + + public set animation(value: string) { + this.setProperty('animation', value); + } + + public get animationDelay(): string { + return this.getPropertyValue('animation-delay'); + } + + public set animationDelay(value: string) { + this.setProperty('animation-delay', value); + } + + public get animationDirection(): string { + return this.getPropertyValue('animation-direction'); + } + + public set animationDirection(value: string) { + this.setProperty('animation-direction', value); + } + + public get animationDuration(): string { + return this.getPropertyValue('animation-duration'); + } + + public set animationDuration(value: string) { + this.setProperty('animation-duration', value); + } + + public get animationFillMode(): string { + return this.getPropertyValue('animation-fill-mode'); + } + + public set animationFillMode(value: string) { + this.setProperty('animation-fill-mode', value); + } + + public get animationIterationCount(): string { + return this.getPropertyValue('animation-iteration-count'); + } + + public set animationIterationCount(value: string) { + this.setProperty('animation-iteration-count', value); + } + + public get animationName(): string { + return this.getPropertyValue('animation-name'); + } + + public set animationName(value: string) { + this.setProperty('animation-name', value); + } + + public get animationPlayState(): string { + return this.getPropertyValue('animation-play-state'); + } + + public set animationPlayState(value: string) { + this.setProperty('animation-play-state', value); + } + + public get animationTimingFunction(): string { + return this.getPropertyValue('animation-timing-function'); + } + + public set animationTimingFunction(value: string) { + this.setProperty('animation-timing-function', value); + } + + public get appearance(): string { + return this.getPropertyValue('appearance'); + } + + public set appearance(value: string) { + this.setProperty('appearance', value); + } + + public get backdropFilter(): string { + return this.getPropertyValue('backdrop-filter'); + } + + public set backdropFilter(value: string) { + this.setProperty('backdrop-filter', value); + } + + public get backfaceVisibility(): string { + return this.getPropertyValue('backface-visibility'); + } + + public set backfaceVisibility(value: string) { + this.setProperty('backface-visibility', value); + } + + public get background(): string { + return this.getPropertyValue('background'); + } + + public set background(value: string) { + this.setProperty('background', value); + } + + public get backgroundAttachment(): string { + return this.getPropertyValue('background-attachment'); + } + + public set backgroundAttachment(value: string) { + this.setProperty('background-attachment', value); + } + + public get backgroundBlendMode(): string { + return this.getPropertyValue('background-blend-mode'); + } + + public set backgroundBlendMode(value: string) { + this.setProperty('background-blend-mode', value); + } + + public get backgroundClip(): string { + return this.getPropertyValue('background-clip'); + } + + public set backgroundClip(value: string) { + this.setProperty('background-clip', value); + } + + public get backgroundColor(): string { + return this.getPropertyValue('background-color'); + } + + public set backgroundColor(value: string) { + this.setProperty('background-color', value); + } + + public get backgroundImage(): string { + return this.getPropertyValue('background-image'); + } + + public set backgroundImage(value: string) { + this.setProperty('background-image', value); + } + + public get backgroundOrigin(): string { + return this.getPropertyValue('background-origin'); + } + + public set backgroundOrigin(value: string) { + this.setProperty('background-origin', value); + } + + public get backgroundPosition(): string { + return this.getPropertyValue('background-position'); + } + + public set backgroundPosition(value: string) { + this.setProperty('background-position', value); + } + + public get backgroundPositionX(): string { + return this.getPropertyValue('background-position-x'); + } + + public set backgroundPositionX(value: string) { + this.setProperty('background-position-x', value); + } + + public get backgroundPositionY(): string { + return this.getPropertyValue('background-position-y'); + } + + public set backgroundPositionY(value: string) { + this.setProperty('background-position-y', value); + } + + public get backgroundRepeat(): string { + return this.getPropertyValue('background-repeat'); + } + + public set backgroundRepeat(value: string) { + this.setProperty('background-repeat', value); + } + + public get backgroundRepeatX(): string { + return this.getPropertyValue('background-repeat-x'); + } + + public set backgroundRepeatX(value: string) { + this.setProperty('background-repeat-x', value); + } + + public get backgroundRepeatY(): string { + return this.getPropertyValue('background-repeat-y'); + } + + public set backgroundRepeatY(value: string) { + this.setProperty('background-repeat-y', value); + } + + public get backgroundSize(): string { + return this.getPropertyValue('background-size'); + } + + public set backgroundSize(value: string) { + this.setProperty('background-size', value); + } + + public get baselineShift(): string { + return this.getPropertyValue('baseline-shift'); + } + + public set baselineShift(value: string) { + this.setProperty('baseline-shift', value); + } + + public get blockSize(): string { + return this.getPropertyValue('block-size'); + } + + public set blockSize(value: string) { + this.setProperty('block-size', value); + } + + public get border(): string { + return this.getPropertyValue('border'); + } + + public set border(value: string) { + this.setProperty('border', value); + } + + public get borderBlockEnd(): string { + return this.getPropertyValue('border-block-end'); + } + + public set borderBlockEnd(value: string) { + this.setProperty('border-block-end', value); + } + + public get borderBlockEndColor(): string { + return this.getPropertyValue('border-block-end-color'); + } + + public set borderBlockEndColor(value: string) { + this.setProperty('border-block-end-color', value); + } + + public get borderBlockEndStyle(): string { + return this.getPropertyValue('border-block-end-style'); + } + + public set borderBlockEndStyle(value: string) { + this.setProperty('border-block-end-style', value); + } + + public get borderBlockEndWidth(): string { + return this.getPropertyValue('border-block-end-width'); + } + + public set borderBlockEndWidth(value: string) { + this.setProperty('border-block-end-width', value); + } + + public get borderBlockStart(): string { + return this.getPropertyValue('border-block-start'); + } + + public set borderBlockStart(value: string) { + this.setProperty('border-block-start', value); + } + + public get borderBlockStartColor(): string { + return this.getPropertyValue('border-block-start-color'); + } + + public set borderBlockStartColor(value: string) { + this.setProperty('border-block-start-color', value); + } + + public get borderBlockStartStyle(): string { + return this.getPropertyValue('border-block-start-style'); + } + + public set borderBlockStartStyle(value: string) { + this.setProperty('border-block-start-style', value); + } + + public get borderBlockStartWidth(): string { + return this.getPropertyValue('border-block-start-width'); + } + + public set borderBlockStartWidth(value: string) { + this.setProperty('border-block-start-width', value); + } + + public get borderBottom(): string { + return this.getPropertyValue('border-bottom'); + } + + public set borderBottom(value: string) { + this.setProperty('border-bottom', value); + } + + public get borderBottomColor(): string { + return this.getPropertyValue('border-bottom-color'); + } + + public set borderBottomColor(value: string) { + this.setProperty('border-bottom-color', value); + } + + public get borderBottomLeftRadius(): string { + return this.getPropertyValue('border-bottom-left-radius'); + } + + public set borderBottomLeftRadius(value: string) { + this.setProperty('border-bottom-left-radius', value); + } + + public get borderBottomRightRadius(): string { + return this.getPropertyValue('border-bottom-right-radius'); + } + + public set borderBottomRightRadius(value: string) { + this.setProperty('border-bottom-right-radius', value); + } + + public get borderBottomStyle(): string { + return this.getPropertyValue('border-bottom-style'); + } + + public set borderBottomStyle(value: string) { + this.setProperty('border-bottom-style', value); + } + + public get borderBottomWidth(): string { + return this.getPropertyValue('border-bottom-width'); + } + + public set borderBottomWidth(value: string) { + this.setProperty('border-bottom-width', value); + } + + public get borderCollapse(): string { + return this.getPropertyValue('border-collapse'); + } + + public set borderCollapse(value: string) { + this.setProperty('border-collapse', value); + } + + public get borderColor(): string { + return this.getPropertyValue('border-color'); + } + + public set borderColor(value: string) { + this.setProperty('border-color', value); + } + + public get borderImage(): string { + return this.getPropertyValue('border-image'); + } + + public set borderImage(value: string) { + this.setProperty('border-image', value); + } + + public get borderImageOutset(): string { + return this.getPropertyValue('border-image-outset'); + } + + public set borderImageOutset(value: string) { + this.setProperty('border-image-outset', value); + } + + public get borderImageRepeat(): string { + return this.getPropertyValue('border-image-repeat'); + } + + public set borderImageRepeat(value: string) { + this.setProperty('border-image-repeat', value); + } + + public get borderImageSlice(): string { + return this.getPropertyValue('border-image-slice'); + } + + public set borderImageSlice(value: string) { + this.setProperty('border-image-slice', value); + } + + public get borderImageSource(): string { + return this.getPropertyValue('border-image-source'); + } + + public set borderImageSource(value: string) { + this.setProperty('border-image-source', value); + } + + public get borderImageWidth(): string { + return this.getPropertyValue('border-image-width'); + } + + public set borderImageWidth(value: string) { + this.setProperty('border-image-width', value); + } + + public get borderInlineEnd(): string { + return this.getPropertyValue('border-inline-end'); + } + + public set borderInlineEnd(value: string) { + this.setProperty('border-inline-end', value); + } + + public get borderInlineEndColor(): string { + return this.getPropertyValue('border-inline-end-color'); + } + + public set borderInlineEndColor(value: string) { + this.setProperty('border-inline-end-color', value); + } + + public get borderInlineEndStyle(): string { + return this.getPropertyValue('border-inline-end-style'); + } + + public set borderInlineEndStyle(value: string) { + this.setProperty('border-inline-end-style', value); + } + + public get borderInlineEndWidth(): string { + return this.getPropertyValue('border-inline-end-width'); + } + + public set borderInlineEndWidth(value: string) { + this.setProperty('border-inline-end-width', value); + } + + public get borderInlineStart(): string { + return this.getPropertyValue('border-inline-start'); + } + + public set borderInlineStart(value: string) { + this.setProperty('border-inline-start', value); + } + + public get borderInlineStartColor(): string { + return this.getPropertyValue('border-inline-start-color'); + } + + public set borderInlineStartColor(value: string) { + this.setProperty('border-inline-start-color', value); + } + + public get borderInlineStartStyle(): string { + return this.getPropertyValue('border-inline-start-style'); + } + + public set borderInlineStartStyle(value: string) { + this.setProperty('border-inline-start-style', value); + } + + public get borderInlineStartWidth(): string { + return this.getPropertyValue('border-inline-start-width'); + } + + public set borderInlineStartWidth(value: string) { + this.setProperty('border-inline-start-width', value); + } + + public get borderLeft(): string { + return this.getPropertyValue('border-left'); + } + + public set borderLeft(value: string) { + this.setProperty('border-left', value); + } + + public get borderLeftColor(): string { + return this.getPropertyValue('border-left-color'); + } + + public set borderLeftColor(value: string) { + this.setProperty('border-left-color', value); + } + + public get borderLeftStyle(): string { + return this.getPropertyValue('border-left-style'); + } + + public set borderLeftStyle(value: string) { + this.setProperty('border-left-style', value); + } + + public get borderLeftWidth(): string { + return this.getPropertyValue('border-left-width'); + } + + public set borderLeftWidth(value: string) { + this.setProperty('border-left-width', value); + } + + public get borderRadius(): string { + return this.getPropertyValue('border-radius'); + } + + public set borderRadius(value: string) { + this.setProperty('border-radius', value); + } + + public get borderRight(): string { + return this.getPropertyValue('border-right'); + } + + public set borderRight(value: string) { + this.setProperty('border-right', value); + } + + public get borderRightColor(): string { + return this.getPropertyValue('border-right-color'); + } + + public set borderRightColor(value: string) { + this.setProperty('border-right-color', value); + } + + public get borderRightStyle(): string { + return this.getPropertyValue('border-right-style'); + } + + public set borderRightStyle(value: string) { + this.setProperty('border-right-style', value); + } + + public get borderRightWidth(): string { + return this.getPropertyValue('border-right-width'); + } + + public set borderRightWidth(value: string) { + this.setProperty('border-right-width', value); + } + + public get borderSpacing(): string { + return this.getPropertyValue('border-spacing'); + } + + public set borderSpacing(value: string) { + this.setProperty('border-spacing', value); + } + + public get borderStyle(): string { + return this.getPropertyValue('border-style'); + } + + public set borderStyle(value: string) { + this.setProperty('border-style', value); + } + + public get borderTop(): string { + return this.getPropertyValue('border-top'); + } + + public set borderTop(value: string) { + this.setProperty('border-top', value); + } + + public get borderTopColor(): string { + return this.getPropertyValue('border-top-color'); + } + + public set borderTopColor(value: string) { + this.setProperty('border-top-color', value); + } + + public get borderTopLeftRadius(): string { + return this.getPropertyValue('border-top-left-radius'); + } + + public set borderTopLeftRadius(value: string) { + this.setProperty('border-top-left-radius', value); + } + + public get borderTopRightRadius(): string { + return this.getPropertyValue('border-top-right-radius'); + } + + public set borderTopRightRadius(value: string) { + this.setProperty('border-top-right-radius', value); + } + + public get borderTopStyle(): string { + return this.getPropertyValue('border-top-style'); + } + + public set borderTopStyle(value: string) { + this.setProperty('border-top-style', value); + } + + public get borderTopWidth(): string { + return this.getPropertyValue('border-top-width'); + } + + public set borderTopWidth(value: string) { + this.setProperty('border-top-width', value); + } + + public get borderWidth(): string { + return this.getPropertyValue('border-width'); + } + + public set borderWidth(value: string) { + this.setProperty('border-width', value); + } + + public get bottom(): string { + return this.getPropertyValue('bottom'); + } + + public set bottom(value: string) { + this.setProperty('bottom', value); + } + + public get boxShadow(): string { + return this.getPropertyValue('box-shadow'); + } + + public set boxShadow(value: string) { + this.setProperty('box-shadow', value); + } + + public get boxSizing(): string { + return this.getPropertyValue('box-sizing'); + } + + public set boxSizing(value: string) { + this.setProperty('box-sizing', value); + } + + public get breakAfter(): string { + return this.getPropertyValue('break-after'); + } + + public set breakAfter(value: string) { + this.setProperty('break-after', value); + } + + public get breakBefore(): string { + return this.getPropertyValue('break-before'); + } + + public set breakBefore(value: string) { + this.setProperty('break-before', value); + } + + public get breakInside(): string { + return this.getPropertyValue('break-inside'); + } + + public set breakInside(value: string) { + this.setProperty('break-inside', value); + } + + public get bufferedRendering(): string { + return this.getPropertyValue('buffered-rendering'); + } + + public set bufferedRendering(value: string) { + this.setProperty('buffered-rendering', value); + } + + public get captionSide(): string { + return this.getPropertyValue('caption-side'); + } + + public set captionSide(value: string) { + this.setProperty('caption-side', value); + } + + public get caretColor(): string { + return this.getPropertyValue('caret-color'); + } + + public set caretColor(value: string) { + this.setProperty('caret-color', value); + } + + public get clear(): string { + return this.getPropertyValue('clear'); + } + + public set clear(value: string) { + this.setProperty('clear', value); + } + + public get clip(): string { + return this.getPropertyValue('clip'); + } + + public set clip(value: string) { + this.setProperty('clip', value); + } + + public get clipPath(): string { + return this.getPropertyValue('clip-path'); + } + + public set clipPath(value: string) { + this.setProperty('clip-path', value); + } + + public get clipRule(): string { + return this.getPropertyValue('clip-rule'); + } + + public set clipRule(value: string) { + this.setProperty('clip-rule', value); + } + + public get color(): string { + return this.getPropertyValue('color'); + } + + public set color(value: string) { + this.setProperty('color', value); + } + + public get colorInterpolation(): string { + return this.getPropertyValue('color-interpolation'); + } + + public set colorInterpolation(value: string) { + this.setProperty('color-interpolation', value); + } + + public get colorInterpolationFilters(): string { + return this.getPropertyValue('color-interpolation-filters'); + } + + public set colorInterpolationFilters(value: string) { + this.setProperty('color-interpolation-filters', value); + } + + public get colorRendering(): string { + return this.getPropertyValue('color-rendering'); + } + + public set colorRendering(value: string) { + this.setProperty('color-rendering', value); + } + + public get colorScheme(): string { + return this.getPropertyValue('color-scheme'); + } + + public set colorScheme(value: string) { + this.setProperty('color-scheme', value); + } + + public get columnCount(): string { + return this.getPropertyValue('column-count'); + } + + public set columnCount(value: string) { + this.setProperty('column-count', value); + } + + public get columnFill(): string { + return this.getPropertyValue('column-fill'); + } + + public set columnFill(value: string) { + this.setProperty('column-fill', value); + } + + public get columnGap(): string { + return this.getPropertyValue('column-gap'); + } + + public set columnGap(value: string) { + this.setProperty('column-gap', value); + } + + public get columnRule(): string { + return this.getPropertyValue('column-rule'); + } + + public set columnRule(value: string) { + this.setProperty('column-rule', value); + } + + public get columnRuleColor(): string { + return this.getPropertyValue('column-rule-color'); + } + + public set columnRuleColor(value: string) { + this.setProperty('column-rule-color', value); + } + + public get columnRuleStyle(): string { + return this.getPropertyValue('column-rule-style'); + } + + public set columnRuleStyle(value: string) { + this.setProperty('column-rule-style', value); + } + + public get columnRuleWidth(): string { + return this.getPropertyValue('column-rule-width'); + } + + public set columnRuleWidth(value: string) { + this.setProperty('column-rule-width', value); + } + + public get columnSpan(): string { + return this.getPropertyValue('column-span'); + } + + public set columnSpan(value: string) { + this.setProperty('column-span', value); + } + + public get columnWidth(): string { + return this.getPropertyValue('column-width'); + } + + public set columnWidth(value: string) { + this.setProperty('column-width', value); + } + + public get columns(): string { + return this.getPropertyValue('columns'); + } + + public set columns(value: string) { + this.setProperty('columns', value); + } + + public get contain(): string { + return this.getPropertyValue('contain'); + } + + public set contain(value: string) { + this.setProperty('contain', value); + } + + public get containIntrinsicSize(): string { + return this.getPropertyValue('contain-intrinsic-size'); + } + + public set containIntrinsicSize(value: string) { + this.setProperty('contain-intrinsic-size', value); + } + + public get content(): string { + return this.getPropertyValue('content'); + } + + public set content(value: string) { + this.setProperty('content', value); + } + + public get contentVisibility(): string { + return this.getPropertyValue('content-visibility'); + } + + public set contentVisibility(value: string) { + this.setProperty('content-visibility', value); + } + + public get counterIncrement(): string { + return this.getPropertyValue('counter-increment'); + } + + public set counterIncrement(value: string) { + this.setProperty('counter-increment', value); + } + + public get counterReset(): string { + return this.getPropertyValue('counter-reset'); + } + + public set counterReset(value: string) { + this.setProperty('counter-reset', value); + } + + public get counterSet(): string { + return this.getPropertyValue('counter-set'); + } + + public set counterSet(value: string) { + this.setProperty('counter-set', value); + } + + public get cssFloat(): string { + return this.getPropertyValue('css-float'); + } + + public set cssFloat(value: string) { + this.setProperty('css-float', value); + } + + public get cursor(): string { + return this.getPropertyValue('cursor'); + } + + public set cursor(value: string) { + this.setProperty('cursor', value); + } + + public get cx(): string { + return this.getPropertyValue('cx'); + } + + public set cx(value: string) { + this.setProperty('cx', value); + } + + public get cy(): string { + return this.getPropertyValue('cy'); + } + + public set cy(value: string) { + this.setProperty('cy', value); + } + + public get d(): string { + return this.getPropertyValue('d'); + } + + public set d(value: string) { + this.setProperty('d', value); + } + + public get direction(): string { + return this.getPropertyValue('direction'); + } + + public set direction(value: string) { + this.setProperty('direction', value); + } + + public get display(): string { + return this.getPropertyValue('display'); + } + + public set display(value: string) { + this.setProperty('display', value); + } + + public get dominantBaseline(): string { + return this.getPropertyValue('dominant-baseline'); + } + + public set dominantBaseline(value: string) { + this.setProperty('dominant-baseline', value); + } + + public get emptyCells(): string { + return this.getPropertyValue('empty-cells'); + } + + public set emptyCells(value: string) { + this.setProperty('empty-cells', value); + } + + public get fill(): string { + return this.getPropertyValue('fill'); + } + + public set fill(value: string) { + this.setProperty('fill', value); + } + + public get fillOpacity(): string { + return this.getPropertyValue('fill-opacity'); + } + + public set fillOpacity(value: string) { + this.setProperty('fill-opacity', value); + } + + public get fillRule(): string { + return this.getPropertyValue('fill-rule'); + } + + public set fillRule(value: string) { + this.setProperty('fill-rule', value); + } + + public get filter(): string { + return this.getPropertyValue('filter'); + } + + public set filter(value: string) { + this.setProperty('filter', value); + } + + public get flex(): string { + return this.getPropertyValue('flex'); + } + + public set flex(value: string) { + this.setProperty('flex', value); + } + + public get flexBasis(): string { + return this.getPropertyValue('flex-basis'); + } + + public set flexBasis(value: string) { + this.setProperty('flex-basis', value); + } + + public get flexDirection(): string { + return this.getPropertyValue('flex-direction'); + } + + public set flexDirection(value: string) { + this.setProperty('flex-direction', value); + } + + public get flexFlow(): string { + return this.getPropertyValue('flex-flow'); + } + + public set flexFlow(value: string) { + this.setProperty('flex-flow', value); + } + + public get flexGrow(): string { + return this.getPropertyValue('flex-grow'); + } + + public set flexGrow(value: string) { + this.setProperty('flex-grow', value); + } + + public get flexShrink(): string { + return this.getPropertyValue('flex-shrink'); + } + + public set flexShrink(value: string) { + this.setProperty('flex-shrink', value); + } + + public get flexWrap(): string { + return this.getPropertyValue('flex-wrap'); + } + + public set flexWrap(value: string) { + this.setProperty('flex-wrap', value); + } + + public get float(): string { + return this.getPropertyValue('float'); + } + + public set float(value: string) { + this.setProperty('float', value); + } + + public get floodColor(): string { + return this.getPropertyValue('flood-color'); + } + + public set floodColor(value: string) { + this.setProperty('flood-color', value); + } + + public get floodOpacity(): string { + return this.getPropertyValue('flood-opacity'); + } + + public set floodOpacity(value: string) { + this.setProperty('flood-opacity', value); + } + + public get font(): string { + return this.getPropertyValue('font'); + } + + public set font(value: string) { + this.setProperty('font', value); + } + + public get fontDisplay(): string { + return this.getPropertyValue('font-display'); + } + + public set fontDisplay(value: string) { + this.setProperty('font-display', value); + } + + public get fontFamily(): string { + return this.getPropertyValue('font-family'); + } + + public set fontFamily(value: string) { + this.setProperty('font-family', value); + } + + public get fontFeatureSettings(): string { + return this.getPropertyValue('font-feature-settings'); + } + + public set fontFeatureSettings(value: string) { + this.setProperty('font-feature-settings', value); + } + + public get fontKerning(): string { + return this.getPropertyValue('font-kerning'); + } + + public set fontKerning(value: string) { + this.setProperty('font-kerning', value); + } + + public get fontOpticalSizing(): string { + return this.getPropertyValue('font-optical-sizing'); + } + + public set fontOpticalSizing(value: string) { + this.setProperty('font-optical-sizing', value); + } + + public get fontSize(): string { + return this.getPropertyValue('font-size'); + } + + public set fontSize(value: string) { + this.setProperty('font-size', value); + } + + public get fontStretch(): string { + return this.getPropertyValue('font-stretch'); + } + + public set fontStretch(value: string) { + this.setProperty('font-stretch', value); + } + + public get fontStyle(): string { + return this.getPropertyValue('font-style'); + } + + public set fontStyle(value: string) { + this.setProperty('font-style', value); + } + + public get fontVariant(): string { + return this.getPropertyValue('font-variant'); + } + + public set fontVariant(value: string) { + this.setProperty('font-variant', value); + } + + public get fontVariantCaps(): string { + return this.getPropertyValue('font-variant-caps'); + } + + public set fontVariantCaps(value: string) { + this.setProperty('font-variant-caps', value); + } + + public get fontVariantEastAsian(): string { + return this.getPropertyValue('font-variant-east-asian'); + } + + public set fontVariantEastAsian(value: string) { + this.setProperty('font-variant-east-asian', value); + } + + public get fontVariantLigatures(): string { + return this.getPropertyValue('font-variant-ligatures'); + } + + public set fontVariantLigatures(value: string) { + this.setProperty('font-variant-ligatures', value); + } + + public get fontVariantNumeric(): string { + return this.getPropertyValue('font-variant-numeric'); + } + + public set fontVariantNumeric(value: string) { + this.setProperty('font-variant-numeric', value); + } + + public get fontVariationSettings(): string { + return this.getPropertyValue('font-variation-settings'); + } + + public set fontVariationSettings(value: string) { + this.setProperty('font-variation-settings', value); + } + + public get fontWeight(): string { + return this.getPropertyValue('font-weight'); + } + + public set fontWeight(value: string) { + this.setProperty('font-weight', value); + } + + public get gap(): string { + return this.getPropertyValue('gap'); + } + + public set gap(value: string) { + this.setProperty('gap', value); + } + + public get grid(): string { + return this.getPropertyValue('grid'); + } + + public set grid(value: string) { + this.setProperty('grid', value); + } + + public get gridArea(): string { + return this.getPropertyValue('grid-area'); + } + + public set gridArea(value: string) { + this.setProperty('grid-area', value); + } + + public get gridAutoColumns(): string { + return this.getPropertyValue('grid-auto-columns'); + } + + public set gridAutoColumns(value: string) { + this.setProperty('grid-auto-columns', value); + } + + public get gridAutoFlow(): string { + return this.getPropertyValue('grid-auto-flow'); + } + + public set gridAutoFlow(value: string) { + this.setProperty('grid-auto-flow', value); + } + + public get gridAutoRows(): string { + return this.getPropertyValue('grid-auto-rows'); + } + + public set gridAutoRows(value: string) { + this.setProperty('grid-auto-rows', value); + } + + public get gridColumn(): string { + return this.getPropertyValue('grid-column'); + } + + public set gridColumn(value: string) { + this.setProperty('grid-column', value); + } + + public get gridColumnEnd(): string { + return this.getPropertyValue('grid-column-end'); + } + + public set gridColumnEnd(value: string) { + this.setProperty('grid-column-end', value); + } + + public get gridColumnGap(): string { + return this.getPropertyValue('grid-column-gap'); + } + + public set gridColumnGap(value: string) { + this.setProperty('grid-column-gap', value); + } + + public get gridColumnStart(): string { + return this.getPropertyValue('grid-column-start'); + } + + public set gridColumnStart(value: string) { + this.setProperty('grid-column-start', value); + } + + public get gridGap(): string { + return this.getPropertyValue('grid-gap'); + } + + public set gridGap(value: string) { + this.setProperty('grid-gap', value); + } + + public get gridRow(): string { + return this.getPropertyValue('grid-row'); + } + + public set gridRow(value: string) { + this.setProperty('grid-row', value); + } + + public get gridRowEnd(): string { + return this.getPropertyValue('grid-row-end'); + } + + public set gridRowEnd(value: string) { + this.setProperty('grid-row-end', value); + } + + public get gridRowGap(): string { + return this.getPropertyValue('grid-row-gap'); + } + + public set gridRowGap(value: string) { + this.setProperty('grid-row-gap', value); + } + + public get gridRowStart(): string { + return this.getPropertyValue('grid-row-start'); + } + + public set gridRowStart(value: string) { + this.setProperty('grid-row-start', value); + } + + public get gridTemplate(): string { + return this.getPropertyValue('grid-template'); + } + + public set gridTemplate(value: string) { + this.setProperty('grid-template', value); + } + + public get gridTemplateAreas(): string { + return this.getPropertyValue('grid-template-areas'); + } + + public set gridTemplateAreas(value: string) { + this.setProperty('grid-template-areas', value); + } + + public get gridTemplateColumns(): string { + return this.getPropertyValue('grid-template-columns'); + } + + public set gridTemplateColumns(value: string) { + this.setProperty('grid-template-columns', value); + } + + public get gridTemplateRows(): string { + return this.getPropertyValue('grid-template-rows'); + } + + public set gridTemplateRows(value: string) { + this.setProperty('grid-template-rows', value); + } + + public get height(): string { + return this.getPropertyValue('height'); + } + + public set height(value: string) { + this.setProperty('height', value); + } + + public get hyphens(): string { + return this.getPropertyValue('hyphens'); + } + + public set hyphens(value: string) { + this.setProperty('hyphens', value); + } + + public get imageOrientation(): string { + return this.getPropertyValue('image-orientation'); + } + + public set imageOrientation(value: string) { + this.setProperty('image-orientation', value); + } + + public get imageRendering(): string { + return this.getPropertyValue('image-rendering'); + } + + public set imageRendering(value: string) { + this.setProperty('image-rendering', value); + } + + public get inherits(): string { + return this.getPropertyValue('inherits'); + } + + public set inherits(value: string) { + this.setProperty('inherits', value); + } + + public get initialValue(): string { + return this.getPropertyValue('initial-value'); + } + + public set initialValue(value: string) { + this.setProperty('initial-value', value); + } + + public get inlineSize(): string { + return this.getPropertyValue('inline-size'); + } + + public set inlineSize(value: string) { + this.setProperty('inline-size', value); + } + + public get isolation(): string { + return this.getPropertyValue('isolation'); + } + + public set isolation(value: string) { + this.setProperty('isolation', value); + } + + public get justifyContent(): string { + return this.getPropertyValue('justify-content'); + } + + public set justifyContent(value: string) { + this.setProperty('justify-content', value); + } + + public get justifyItems(): string { + return this.getPropertyValue('justify-items'); + } + + public set justifyItems(value: string) { + this.setProperty('justify-items', value); + } + + public get justifySelf(): string { + return this.getPropertyValue('justify-self'); + } + + public set justifySelf(value: string) { + this.setProperty('justify-self', value); + } + + public get left(): string { + return this.getPropertyValue('left'); + } + + public set left(value: string) { + this.setProperty('left', value); + } + + public get letterSpacing(): string { + return this.getPropertyValue('letter-spacing'); + } + + public set letterSpacing(value: string) { + this.setProperty('letter-spacing', value); + } + + public get lightingColor(): string { + return this.getPropertyValue('lighting-color'); + } + + public set lightingColor(value: string) { + this.setProperty('lighting-color', value); + } + + public get lineBreak(): string { + return this.getPropertyValue('line-break'); + } + + public set lineBreak(value: string) { + this.setProperty('line-break', value); + } + + public get lineHeight(): string { + return this.getPropertyValue('line-height'); + } + + public set lineHeight(value: string) { + this.setProperty('line-height', value); + } + + public get listStyle(): string { + return this.getPropertyValue('list-style'); + } + + public set listStyle(value: string) { + this.setProperty('list-style', value); + } + + public get listStyleImage(): string { + return this.getPropertyValue('list-style-image'); + } + + public set listStyleImage(value: string) { + this.setProperty('list-style-image', value); + } + + public get listStylePosition(): string { + return this.getPropertyValue('list-style-position'); + } + + public set listStylePosition(value: string) { + this.setProperty('list-style-position', value); + } + + public get listStyleType(): string { + return this.getPropertyValue('list-style-type'); + } + + public set listStyleType(value: string) { + this.setProperty('list-style-type', value); + } + + public get margin(): string { + return this.getPropertyValue('margin'); + } + + public set margin(value: string) { + this.setProperty('margin', value); + } + + public get marginBlockEnd(): string { + return this.getPropertyValue('margin-block-end'); + } + + public set marginBlockEnd(value: string) { + this.setProperty('margin-block-end', value); + } + + public get marginBlockStart(): string { + return this.getPropertyValue('margin-block-start'); + } + + public set marginBlockStart(value: string) { + this.setProperty('margin-block-start', value); + } + + public get marginBottom(): string { + return this.getPropertyValue('margin-bottom'); + } + + public set marginBottom(value: string) { + this.setProperty('margin-bottom', value); + } + + public get marginInlineEnd(): string { + return this.getPropertyValue('margin-inline-end'); + } + + public set marginInlineEnd(value: string) { + this.setProperty('margin-inline-end', value); + } + + public get marginInlineStart(): string { + return this.getPropertyValue('margin-inline-start'); + } + + public set marginInlineStart(value: string) { + this.setProperty('margin-inline-start', value); + } + + public get marginLeft(): string { + return this.getPropertyValue('margin-left'); + } + + public set marginLeft(value: string) { + this.setProperty('margin-left', value); + } + + public get marginRight(): string { + return this.getPropertyValue('margin-right'); + } + + public set marginRight(value: string) { + this.setProperty('margin-right', value); + } + + public get marginTop(): string { + return this.getPropertyValue('margin-top'); + } + + public set marginTop(value: string) { + this.setProperty('margin-top', value); + } + + public get marker(): string { + return this.getPropertyValue('marker'); + } + + public set marker(value: string) { + this.setProperty('marker', value); + } + + public get markerEnd(): string { + return this.getPropertyValue('marker-end'); + } + + public set markerEnd(value: string) { + this.setProperty('marker-end', value); + } + + public get markerMid(): string { + return this.getPropertyValue('marker-mid'); + } + + public set markerMid(value: string) { + this.setProperty('marker-mid', value); + } + + public get markerStart(): string { + return this.getPropertyValue('marker-start'); + } + + public set markerStart(value: string) { + this.setProperty('marker-start', value); + } + + public get mask(): string { + return this.getPropertyValue('mask'); + } + + public set mask(value: string) { + this.setProperty('mask', value); + } + + public get maskType(): string { + return this.getPropertyValue('mask-type'); + } + + public set maskType(value: string) { + this.setProperty('mask-type', value); + } + + public get maxBlockSize(): string { + return this.getPropertyValue('max-block-size'); + } + + public set maxBlockSize(value: string) { + this.setProperty('max-block-size', value); + } + + public get maxHeight(): string { + return this.getPropertyValue('max-height'); + } + + public set maxHeight(value: string) { + this.setProperty('max-height', value); + } + + public get maxInlineSize(): string { + return this.getPropertyValue('max-inline-size'); + } + + public set maxInlineSize(value: string) { + this.setProperty('max-inline-size', value); + } + + public get maxWidth(): string { + return this.getPropertyValue('max-width'); + } + + public set maxWidth(value: string) { + this.setProperty('max-width', value); + } + + public get maxZoom(): string { + return this.getPropertyValue('max-zoom'); + } + + public set maxZoom(value: string) { + this.setProperty('max-zoom', value); + } + + public get minBlockSize(): string { + return this.getPropertyValue('min-block-size'); + } + + public set minBlockSize(value: string) { + this.setProperty('min-block-size', value); + } + + public get minHeight(): string { + return this.getPropertyValue('min-height'); + } + + public set minHeight(value: string) { + this.setProperty('min-height', value); + } + + public get minInlineSize(): string { + return this.getPropertyValue('min-inline-size'); + } + + public set minInlineSize(value: string) { + this.setProperty('min-inline-size', value); + } + + public get minWidth(): string { + return this.getPropertyValue('min-width'); + } + + public set minWidth(value: string) { + this.setProperty('min-width', value); + } + + public get minZoom(): string { + return this.getPropertyValue('min-zoom'); + } + + public set minZoom(value: string) { + this.setProperty('min-zoom', value); + } + + public get mixBlendMode(): string { + return this.getPropertyValue('mix-blend-mode'); + } + + public set mixBlendMode(value: string) { + this.setProperty('mix-blend-mode', value); + } + + public get objectFit(): string { + return this.getPropertyValue('object-fit'); + } + + public set objectFit(value: string) { + this.setProperty('object-fit', value); + } + + public get objectPosition(): string { + return this.getPropertyValue('object-position'); + } + + public set objectPosition(value: string) { + this.setProperty('object-position', value); + } + + public get offset(): string { + return this.getPropertyValue('offset'); + } + + public set offset(value: string) { + this.setProperty('offset', value); + } + + public get offsetDistance(): string { + return this.getPropertyValue('offset-distance'); + } + + public set offsetDistance(value: string) { + this.setProperty('offset-distance', value); + } + + public get offsetPath(): string { + return this.getPropertyValue('offset-path'); + } + + public set offsetPath(value: string) { + this.setProperty('offset-path', value); + } + + public get offsetRotate(): string { + return this.getPropertyValue('offset-rotate'); + } + + public set offsetRotate(value: string) { + this.setProperty('offset-rotate', value); + } + + public get opacity(): string { + return this.getPropertyValue('opacity'); + } + + public set opacity(value: string) { + this.setProperty('opacity', value); + } + + public get order(): string { + return this.getPropertyValue('order'); + } + + public set order(value: string) { + this.setProperty('order', value); + } + + public get orientation(): string { + return this.getPropertyValue('orientation'); + } + + public set orientation(value: string) { + this.setProperty('orientation', value); + } + + public get orphans(): string { + return this.getPropertyValue('orphans'); + } + + public set orphans(value: string) { + this.setProperty('orphans', value); + } + + public get outline(): string { + return this.getPropertyValue('outline'); + } + + public set outline(value: string) { + this.setProperty('outline', value); + } + + public get outlineColor(): string { + return this.getPropertyValue('outline-color'); + } + + public set outlineColor(value: string) { + this.setProperty('outline-color', value); + } + + public get outlineOffset(): string { + return this.getPropertyValue('outline-offset'); + } + + public set outlineOffset(value: string) { + this.setProperty('outline-offset', value); + } + + public get outlineStyle(): string { + return this.getPropertyValue('outline-style'); + } + + public set outlineStyle(value: string) { + this.setProperty('outline-style', value); + } + + public get outlineWidth(): string { + return this.getPropertyValue('outline-width'); + } + + public set outlineWidth(value: string) { + this.setProperty('outline-width', value); + } + + public get overflow(): string { + return this.getPropertyValue('overflow'); + } + + public set overflow(value: string) { + this.setProperty('overflow', value); + } + + public get overflowAnchor(): string { + return this.getPropertyValue('overflow-anchor'); + } + + public set overflowAnchor(value: string) { + this.setProperty('overflow-anchor', value); + } + + public get overflowWrap(): string { + return this.getPropertyValue('overflow-wrap'); + } + + public set overflowWrap(value: string) { + this.setProperty('overflow-wrap', value); + } + + public get overflowX(): string { + return this.getPropertyValue('overflow-x'); + } + + public set overflowX(value: string) { + this.setProperty('overflow-x', value); + } + + public get overflowY(): string { + return this.getPropertyValue('overflow-y'); + } + + public set overflowY(value: string) { + this.setProperty('overflow-y', value); + } + + public get overscrollBehavior(): string { + return this.getPropertyValue('overscroll-behavior'); + } + + public set overscrollBehavior(value: string) { + this.setProperty('overscroll-behavior', value); + } + + public get overscrollBehaviorBlock(): string { + return this.getPropertyValue('overscroll-behavior-block'); + } + + public set overscrollBehaviorBlock(value: string) { + this.setProperty('overscroll-behavior-block', value); + } + + public get overscrollBehaviorInline(): string { + return this.getPropertyValue('overscroll-behavior-inline'); + } + + public set overscrollBehaviorInline(value: string) { + this.setProperty('overscroll-behavior-inline', value); + } + + public get overscrollBehaviorX(): string { + return this.getPropertyValue('overscroll-behavior-x'); + } + + public set overscrollBehaviorX(value: string) { + this.setProperty('overscroll-behavior-x', value); + } + + public get overscrollBehaviorY(): string { + return this.getPropertyValue('overscroll-behavior-y'); + } + + public set overscrollBehaviorY(value: string) { + this.setProperty('overscroll-behavior-y', value); + } + + public get padding(): string { + return this.getPropertyValue('padding'); + } + + public set padding(value: string) { + this.setProperty('padding', value); + } + + public get paddingBlockEnd(): string { + return this.getPropertyValue('padding-block-end'); + } + + public set paddingBlockEnd(value: string) { + this.setProperty('padding-block-end', value); + } + + public get paddingBlockStart(): string { + return this.getPropertyValue('padding-block-start'); + } + + public set paddingBlockStart(value: string) { + this.setProperty('padding-block-start', value); + } + + public get paddingBottom(): string { + return this.getPropertyValue('padding-bottom'); + } + + public set paddingBottom(value: string) { + this.setProperty('padding-bottom', value); + } + + public get paddingInlineEnd(): string { + return this.getPropertyValue('padding-inline-end'); + } + + public set paddingInlineEnd(value: string) { + this.setProperty('padding-inline-end', value); + } + + public get paddingInlineStart(): string { + return this.getPropertyValue('padding-inline-start'); + } + + public set paddingInlineStart(value: string) { + this.setProperty('padding-inline-start', value); + } + + public get paddingLeft(): string { + return this.getPropertyValue('padding-left'); + } + + public set paddingLeft(value: string) { + this.setProperty('padding-left', value); + } + + public get paddingRight(): string { + return this.getPropertyValue('padding-right'); + } + + public set paddingRight(value: string) { + this.setProperty('padding-right', value); + } + + public get paddingTop(): string { + return this.getPropertyValue('padding-top'); + } + + public set paddingTop(value: string) { + this.setProperty('padding-top', value); + } + + public get page(): string { + return this.getPropertyValue('page'); + } + + public set page(value: string) { + this.setProperty('page', value); + } + + public get pageBreakAfter(): string { + return this.getPropertyValue('page-break-after'); + } + + public set pageBreakAfter(value: string) { + this.setProperty('page-break-after', value); + } + + public get pageBreakBefore(): string { + return this.getPropertyValue('page-break-before'); + } + + public set pageBreakBefore(value: string) { + this.setProperty('page-break-before', value); + } + + public get pageBreakInside(): string { + return this.getPropertyValue('page-break-inside'); + } + + public set pageBreakInside(value: string) { + this.setProperty('page-break-inside', value); + } + + public get pageOrientation(): string { + return this.getPropertyValue('page-orientation'); + } + + public set pageOrientation(value: string) { + this.setProperty('page-orientation', value); + } + + public get paintOrder(): string { + return this.getPropertyValue('paint-order'); + } + + public set paintOrder(value: string) { + this.setProperty('paint-order', value); + } + + public get perspective(): string { + return this.getPropertyValue('perspective'); + } + + public set perspective(value: string) { + this.setProperty('perspective', value); + } + + public get perspectiveOrigin(): string { + return this.getPropertyValue('perspective-origin'); + } + + public set perspectiveOrigin(value: string) { + this.setProperty('perspective-origin', value); + } + + public get placeContent(): string { + return this.getPropertyValue('place-content'); + } + + public set placeContent(value: string) { + this.setProperty('place-content', value); + } + + public get placeItems(): string { + return this.getPropertyValue('place-items'); + } + + public set placeItems(value: string) { + this.setProperty('place-items', value); + } + + public get placeSelf(): string { + return this.getPropertyValue('place-self'); + } + + public set placeSelf(value: string) { + this.setProperty('place-self', value); + } + + public get pointerEvents(): string { + return this.getPropertyValue('pointer-events'); + } + + public set pointerEvents(value: string) { + this.setProperty('pointer-events', value); + } + + public get position(): string { + return this.getPropertyValue('position'); + } + + public set position(value: string) { + this.setProperty('position', value); + } + + public get quotes(): string { + return this.getPropertyValue('quotes'); + } + + public set quotes(value: string) { + this.setProperty('quotes', value); + } + + public get r(): string { + return this.getPropertyValue('r'); + } + + public set r(value: string) { + this.setProperty('r', value); + } + + public get resize(): string { + return this.getPropertyValue('resize'); + } + + public set resize(value: string) { + this.setProperty('resize', value); + } + + public get right(): string { + return this.getPropertyValue('right'); + } + + public set right(value: string) { + this.setProperty('right', value); + } + + public get rowGap(): string { + return this.getPropertyValue('row-gap'); + } + + public set rowGap(value: string) { + this.setProperty('row-gap', value); + } + + public get rubyPosition(): string { + return this.getPropertyValue('ruby-position'); + } + + public set rubyPosition(value: string) { + this.setProperty('ruby-position', value); + } + + public get rx(): string { + return this.getPropertyValue('rx'); + } + + public set rx(value: string) { + this.setProperty('rx', value); + } + + public get ry(): string { + return this.getPropertyValue('ry'); + } + + public set ry(value: string) { + this.setProperty('ry', value); + } + + public get scrollBehavior(): string { + return this.getPropertyValue('scroll-behavior'); + } + + public set scrollBehavior(value: string) { + this.setProperty('scroll-behavior', value); + } + + public get scrollMargin(): string { + return this.getPropertyValue('scroll-margin'); + } + + public set scrollMargin(value: string) { + this.setProperty('scroll-margin', value); + } + + public get scrollMarginBlock(): string { + return this.getPropertyValue('scroll-margin-block'); + } + + public set scrollMarginBlock(value: string) { + this.setProperty('scroll-margin-block', value); + } + + public get scrollMarginBlockEnd(): string { + return this.getPropertyValue('scroll-margin-block-end'); + } + + public set scrollMarginBlockEnd(value: string) { + this.setProperty('scroll-margin-block-end', value); + } + + public get scrollMarginBlockStart(): string { + return this.getPropertyValue('scroll-margin-block-start'); + } + + public set scrollMarginBlockStart(value: string) { + this.setProperty('scroll-margin-block-start', value); + } + + public get scrollMarginBottom(): string { + return this.getPropertyValue('scroll-margin-bottom'); + } + + public set scrollMarginBottom(value: string) { + this.setProperty('scroll-margin-bottom', value); + } + + public get scrollMarginInline(): string { + return this.getPropertyValue('scroll-margin-inline'); + } + + public set scrollMarginInline(value: string) { + this.setProperty('scroll-margin-inline', value); + } + + public get scrollMarginInlineEnd(): string { + return this.getPropertyValue('scroll-margin-inline-end'); + } + + public set scrollMarginInlineEnd(value: string) { + this.setProperty('scroll-margin-inline-end', value); + } + + public get scrollMarginInlineStart(): string { + return this.getPropertyValue('scroll-margin-inline-start'); + } + + public set scrollMarginInlineStart(value: string) { + this.setProperty('scroll-margin-inline-start', value); + } + + public get scrollMarginLeft(): string { + return this.getPropertyValue('scroll-margin-left'); + } + + public set scrollMarginLeft(value: string) { + this.setProperty('scroll-margin-left', value); + } + + public get scrollMarginRight(): string { + return this.getPropertyValue('scroll-margin-right'); + } + + public set scrollMarginRight(value: string) { + this.setProperty('scroll-margin-right', value); + } + + public get scrollMarginTop(): string { + return this.getPropertyValue('scroll-margin-top'); + } + + public set scrollMarginTop(value: string) { + this.setProperty('scroll-margin-top', value); + } + + public get scrollPadding(): string { + return this.getPropertyValue('scroll-padding'); + } + + public set scrollPadding(value: string) { + this.setProperty('scroll-padding', value); + } + + public get scrollPaddingBlock(): string { + return this.getPropertyValue('scroll-padding-block'); + } + + public set scrollPaddingBlock(value: string) { + this.setProperty('scroll-padding-block', value); + } + + public get scrollPaddingBlockEnd(): string { + return this.getPropertyValue('scroll-padding-block-end'); + } + + public set scrollPaddingBlockEnd(value: string) { + this.setProperty('scroll-padding-block-end', value); + } + + public get scrollPaddingBlockStart(): string { + return this.getPropertyValue('scroll-padding-block-start'); + } + + public set scrollPaddingBlockStart(value: string) { + this.setProperty('scroll-padding-block-start', value); + } + + public get scrollPaddingBottom(): string { + return this.getPropertyValue('scroll-padding-bottom'); + } + + public set scrollPaddingBottom(value: string) { + this.setProperty('scroll-padding-bottom', value); + } + + public get scrollPaddingInline(): string { + return this.getPropertyValue('scroll-padding-inline'); + } + + public set scrollPaddingInline(value: string) { + this.setProperty('scroll-padding-inline', value); + } + + public get scrollPaddingInlineEnd(): string { + return this.getPropertyValue('scroll-padding-inline-end'); + } + + public set scrollPaddingInlineEnd(value: string) { + this.setProperty('scroll-padding-inline-end', value); + } + + public get scrollPaddingInlineStart(): string { + return this.getPropertyValue('scroll-padding-inline-start'); + } + + public set scrollPaddingInlineStart(value: string) { + this.setProperty('scroll-padding-inline-start', value); + } + + public get scrollPaddingLeft(): string { + return this.getPropertyValue('scroll-padding-left'); + } + + public set scrollPaddingLeft(value: string) { + this.setProperty('scroll-padding-left', value); + } + + public get scrollPaddingRight(): string { + return this.getPropertyValue('scroll-padding-right'); + } + + public set scrollPaddingRight(value: string) { + this.setProperty('scroll-padding-right', value); + } + + public get scrollPaddingTop(): string { + return this.getPropertyValue('scroll-padding-top'); + } + + public set scrollPaddingTop(value: string) { + this.setProperty('scroll-padding-top', value); + } + + public get scrollSnapAlign(): string { + return this.getPropertyValue('scroll-snap-align'); + } + + public set scrollSnapAlign(value: string) { + this.setProperty('scroll-snap-align', value); + } + + public get scrollSnapStop(): string { + return this.getPropertyValue('scroll-snap-stop'); + } + + public set scrollSnapStop(value: string) { + this.setProperty('scroll-snap-stop', value); + } + + public get scrollSnapType(): string { + return this.getPropertyValue('scroll-snap-type'); + } + + public set scrollSnapType(value: string) { + this.setProperty('scroll-snap-type', value); + } + + public get shapeImageThreshold(): string { + return this.getPropertyValue('shape-image-threshold'); + } + + public set shapeImageThreshold(value: string) { + this.setProperty('shape-image-threshold', value); + } + + public get shapeMargin(): string { + return this.getPropertyValue('shape-margin'); + } + + public set shapeMargin(value: string) { + this.setProperty('shape-margin', value); + } + + public get shapeOutside(): string { + return this.getPropertyValue('shape-outside'); + } + + public set shapeOutside(value: string) { + this.setProperty('shape-outside', value); + } + + public get shapeRendering(): string { + return this.getPropertyValue('shape-rendering'); + } + + public set shapeRendering(value: string) { + this.setProperty('shape-rendering', value); + } + + public get size(): string { + return this.getPropertyValue('size'); + } + + public set size(value: string) { + this.setProperty('size', value); + } + + public get speak(): string { + return this.getPropertyValue('speak'); + } + + public set speak(value: string) { + this.setProperty('speak', value); + } + + public get src(): string { + return this.getPropertyValue('src'); + } + + public set src(value: string) { + this.setProperty('src', value); + } + + public get stopColor(): string { + return this.getPropertyValue('stop-color'); + } + + public set stopColor(value: string) { + this.setProperty('stop-color', value); + } + + public get stopOpacity(): string { + return this.getPropertyValue('stop-opacity'); + } + + public set stopOpacity(value: string) { + this.setProperty('stop-opacity', value); + } + + public get stroke(): string { + return this.getPropertyValue('stroke'); + } + + public set stroke(value: string) { + this.setProperty('stroke', value); + } + + public get strokeDasharray(): string { + return this.getPropertyValue('stroke-dasharray'); + } + + public set strokeDasharray(value: string) { + this.setProperty('stroke-dasharray', value); + } + + public get strokeDashoffset(): string { + return this.getPropertyValue('stroke-dashoffset'); + } + + public set strokeDashoffset(value: string) { + this.setProperty('stroke-dashoffset', value); + } + + public get strokeLinecap(): string { + return this.getPropertyValue('stroke-linecap'); + } + + public set strokeLinecap(value: string) { + this.setProperty('stroke-linecap', value); + } + + public get strokeLinejoin(): string { + return this.getPropertyValue('stroke-linejoin'); + } + + public set strokeLinejoin(value: string) { + this.setProperty('stroke-linejoin', value); + } + + public get strokeMiterlimit(): string { + return this.getPropertyValue('stroke-miterlimit'); + } + + public set strokeMiterlimit(value: string) { + this.setProperty('stroke-miterlimit', value); + } + + public get strokeOpacity(): string { + return this.getPropertyValue('stroke-opacity'); + } + + public set strokeOpacity(value: string) { + this.setProperty('stroke-opacity', value); + } + + public get strokeWidth(): string { + return this.getPropertyValue('stroke-width'); + } + + public set strokeWidth(value: string) { + this.setProperty('stroke-width', value); + } + + public get syntax(): string { + return this.getPropertyValue('syntax'); + } + + public set syntax(value: string) { + this.setProperty('syntax', value); + } + + public get tabSize(): string { + return this.getPropertyValue('tab-size'); + } + + public set tabSize(value: string) { + this.setProperty('tab-size', value); + } + + public get tableLayout(): string { + return this.getPropertyValue('table-layout'); + } + + public set tableLayout(value: string) { + this.setProperty('table-layout', value); + } + + public get textAlign(): string { + return this.getPropertyValue('text-align'); + } + + public set textAlign(value: string) { + this.setProperty('text-align', value); + } + + public get textAlignLast(): string { + return this.getPropertyValue('text-align-last'); + } + + public set textAlignLast(value: string) { + this.setProperty('text-align-last', value); + } + + public get textAnchor(): string { + return this.getPropertyValue('text-anchor'); + } + + public set textAnchor(value: string) { + this.setProperty('text-anchor', value); + } + + public get textCombineUpright(): string { + return this.getPropertyValue('text-combine-upright'); + } + + public set textCombineUpright(value: string) { + this.setProperty('text-combine-upright', value); + } + + public get textDecoration(): string { + return this.getPropertyValue('text-decoration'); + } + + public set textDecoration(value: string) { + this.setProperty('text-decoration', value); + } + + public get textDecorationColor(): string { + return this.getPropertyValue('text-decoration-color'); + } + + public set textDecorationColor(value: string) { + this.setProperty('text-decoration-color', value); + } + + public get textDecorationLine(): string { + return this.getPropertyValue('text-decoration-line'); + } + + public set textDecorationLine(value: string) { + this.setProperty('text-decoration-line', value); + } + + public get textDecorationSkipInk(): string { + return this.getPropertyValue('text-decoration-skip-ink'); + } + + public set textDecorationSkipInk(value: string) { + this.setProperty('text-decoration-skip-ink', value); + } + + public get textDecorationStyle(): string { + return this.getPropertyValue('text-decoration-style'); + } + + public set textDecorationStyle(value: string) { + this.setProperty('text-decoration-style', value); + } + + public get textIndent(): string { + return this.getPropertyValue('text-indent'); + } + + public set textIndent(value: string) { + this.setProperty('text-indent', value); + } + + public get textOrientation(): string { + return this.getPropertyValue('text-orientation'); + } + + public set textOrientation(value: string) { + this.setProperty('text-orientation', value); + } + + public get textOverflow(): string { + return this.getPropertyValue('text-overflow'); + } + + public set textOverflow(value: string) { + this.setProperty('text-overflow', value); + } + + public get textRendering(): string { + return this.getPropertyValue('text-rendering'); + } + + public set textRendering(value: string) { + this.setProperty('text-rendering', value); + } + + public get textShadow(): string { + return this.getPropertyValue('text-shadow'); + } + + public set textShadow(value: string) { + this.setProperty('text-shadow', value); + } + + public get textSizeAdjust(): string { + return this.getPropertyValue('text-size-adjust'); + } + + public set textSizeAdjust(value: string) { + this.setProperty('text-size-adjust', value); + } + + public get textTransform(): string { + return this.getPropertyValue('text-transform'); + } + + public set textTransform(value: string) { + this.setProperty('text-transform', value); + } + + public get textUnderlinePosition(): string { + return this.getPropertyValue('text-underline-position'); + } + + public set textUnderlinePosition(value: string) { + this.setProperty('text-underline-position', value); + } + + public get top(): string { + return this.getPropertyValue('top'); + } + + public set top(value: string) { + this.setProperty('top', value); + } + + public get touchAction(): string { + return this.getPropertyValue('touch-action'); + } + + public set touchAction(value: string) { + this.setProperty('touch-action', value); + } + + public get transform(): string { + return this.getPropertyValue('transform'); + } + + public set transform(value: string) { + this.setProperty('transform', value); + } + + public get transformBox(): string { + return this.getPropertyValue('transform-box'); + } + + public set transformBox(value: string) { + this.setProperty('transform-box', value); + } + + public get transformOrigin(): string { + return this.getPropertyValue('transform-origin'); + } + + public set transformOrigin(value: string) { + this.setProperty('transform-origin', value); + } + + public get transformStyle(): string { + return this.getPropertyValue('transform-style'); + } + + public set transformStyle(value: string) { + this.setProperty('transform-style', value); + } + + public get transition(): string { + return this.getPropertyValue('transition'); + } + + public set transition(value: string) { + this.setProperty('transition', value); + } + + public get transitionDelay(): string { + return this.getPropertyValue('transition-delay'); + } + + public set transitionDelay(value: string) { + this.setProperty('transition-delay', value); + } + + public get transitionDuration(): string { + return this.getPropertyValue('transition-duration'); + } + + public set transitionDuration(value: string) { + this.setProperty('transition-duration', value); + } + + public get transitionProperty(): string { + return this.getPropertyValue('transition-property'); + } + + public set transitionProperty(value: string) { + this.setProperty('transition-property', value); + } + + public get transitionTimingFunction(): string { + return this.getPropertyValue('transition-timing-function'); + } + + public set transitionTimingFunction(value: string) { + this.setProperty('transition-timing-function', value); + } + + public get unicodeBidi(): string { + return this.getPropertyValue('unicode-bidi'); + } + + public set unicodeBidi(value: string) { + this.setProperty('unicode-bidi', value); + } + + public get unicodeRange(): string { + return this.getPropertyValue('unicode-range'); + } + + public set unicodeRange(value: string) { + this.setProperty('unicode-range', value); + } + + public get userSelect(): string { + return this.getPropertyValue('user-select'); + } + + public set userSelect(value: string) { + this.setProperty('user-select', value); + } + + public get userZoom(): string { + return this.getPropertyValue('user-zoom'); + } + + public set userZoom(value: string) { + this.setProperty('user-zoom', value); + } + + public get vectorEffect(): string { + return this.getPropertyValue('vector-effect'); + } + + public set vectorEffect(value: string) { + this.setProperty('vector-effect', value); + } + + public get verticalAlign(): string { + return this.getPropertyValue('vertical-align'); + } + + public set verticalAlign(value: string) { + this.setProperty('vertical-align', value); + } + + public get visibility(): string { + return this.getPropertyValue('visibility'); + } + + public set visibility(value: string) { + this.setProperty('visibility', value); + } + + public get whiteSpace(): string { + return this.getPropertyValue('white-space'); + } + + public set whiteSpace(value: string) { + this.setProperty('white-space', value); + } + + public get widows(): string { + return this.getPropertyValue('widows'); + } + + public set widows(value: string) { + this.setProperty('widows', value); + } + + public get width(): string { + return this.getPropertyValue('width'); + } + + public set width(value: string) { + this.setProperty('width', value); + } + + public get willChange(): string { + return this.getPropertyValue('will-change'); + } + + public set willChange(value: string) { + this.setProperty('will-change', value); + } + + public get wordBreak(): string { + return this.getPropertyValue('word-break'); + } + + public set wordBreak(value: string) { + this.setProperty('word-break', value); + } + + public get wordSpacing(): string { + return this.getPropertyValue('word-spacing'); + } + + public set wordSpacing(value: string) { + this.setProperty('word-spacing', value); + } + + public get wordWrap(): string { + return this.getPropertyValue('word-wrap'); + } + + public set wordWrap(value: string) { + this.setProperty('word-wrap', value); + } + + public get writingMode(): string { + return this.getPropertyValue('writing-mode'); + } + + public set writingMode(value: string) { + this.setProperty('writing-mode', value); + } + + public get x(): string { + return this.getPropertyValue('x'); + } + + public set x(value: string) { + this.setProperty('x', value); + } + + public get y(): string { + return this.getPropertyValue('y'); + } + + public set y(value: string) { + this.setProperty('y', value); + } + + public get zIndex(): string { + return this.getPropertyValue('z-index'); + } + + public set zIndex(value: string) { + this.setProperty('z-index', value); + } + + public get zoom(): string { + return this.getPropertyValue('zoom'); + } + + public set zoom(value: string) { + this.setProperty('zoom', value); + } +} diff --git a/packages/happy-dom/src/css/declaration/CSSStyleDeclarationUtility.ts b/packages/happy-dom/src/css/declaration/CSSStyleDeclarationUtility.ts new file mode 100644 index 000000000..c364d1098 --- /dev/null +++ b/packages/happy-dom/src/css/declaration/CSSStyleDeclarationUtility.ts @@ -0,0 +1,54 @@ +/** + * CSS Style Declaration utility + */ +export default class CSSStyleDeclarationUtility { + /** + * Converts style string to object. + * + * @param styleString Style string (e.g. "border: 2px solid red; font-size: 12px;"). + * @param [cache] Cache. + * @returns Style object. + */ + public static styleStringToObject(styleString: string): { [k: string]: string } { + const styles = {}; + + if (styleString) { + const parts = styleString.split(';'); + + 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) { + styles[trimmedName] = trimmedValue; + } + } + } + } + } + + return styles; + } + + /** + * Converts style object to string. + * + * @param styleObject Style object. + * @returns Styles as string. + */ + public static styleObjectToString(styleObject: { [k: string]: string }): string { + const keys = Object.keys(styleObject); + let styleString = ''; + + for (const key of keys) { + if (styleString) { + styleString += ' '; + } + styleString += `${key}: ${styleObject[key]};`; + } + + return styleString; + } +} diff --git a/packages/happy-dom/src/css/rules/CSSFontFaceRule.ts b/packages/happy-dom/src/css/rules/CSSFontFaceRule.ts index fe3246f76..35f96c17d 100644 --- a/packages/happy-dom/src/css/rules/CSSFontFaceRule.ts +++ b/packages/happy-dom/src/css/rules/CSSFontFaceRule.ts @@ -1,5 +1,5 @@ import CSSRule from '../CSSRule'; -import CSSStyleDeclaration from '../CSSStyleDeclaration'; +import CSSStyleDeclaration from '../declaration/CSSStyleDeclaration'; /** * CSSRule interface. diff --git a/packages/happy-dom/src/css/rules/CSSKeyframeRule.ts b/packages/happy-dom/src/css/rules/CSSKeyframeRule.ts index 07d6dd0e6..3b7159cc1 100644 --- a/packages/happy-dom/src/css/rules/CSSKeyframeRule.ts +++ b/packages/happy-dom/src/css/rules/CSSKeyframeRule.ts @@ -1,5 +1,5 @@ import CSSRule from '../CSSRule'; -import CSSStyleDeclaration from '../CSSStyleDeclaration'; +import CSSStyleDeclaration from '../declaration/CSSStyleDeclaration'; /** * CSSRule interface. diff --git a/packages/happy-dom/src/css/rules/CSSKeyframesRule.ts b/packages/happy-dom/src/css/rules/CSSKeyframesRule.ts index af9a89874..cac17f5ea 100644 --- a/packages/happy-dom/src/css/rules/CSSKeyframesRule.ts +++ b/packages/happy-dom/src/css/rules/CSSKeyframesRule.ts @@ -1,5 +1,5 @@ import CSSRule from '../CSSRule'; -import CSSStyleDeclaration from '../CSSStyleDeclaration'; +import CSSStyleDeclaration from '../declaration/CSSStyleDeclaration'; import CSSKeyframeRule from './CSSKeyframeRule'; const CSS_RULE_REGEXP = /([^{]+){([^}]+)}/; diff --git a/packages/happy-dom/src/css/rules/CSSStyleRule.ts b/packages/happy-dom/src/css/rules/CSSStyleRule.ts index cda9019d2..c6308e7e7 100644 --- a/packages/happy-dom/src/css/rules/CSSStyleRule.ts +++ b/packages/happy-dom/src/css/rules/CSSStyleRule.ts @@ -1,5 +1,5 @@ import CSSRule from '../CSSRule'; -import CSSStyleDeclaration from '../CSSStyleDeclaration'; +import CSSStyleDeclaration from '../declaration/CSSStyleDeclaration'; /** * CSSRule interface. diff --git a/packages/happy-dom/src/event/Event.ts b/packages/happy-dom/src/event/Event.ts index 73ce1f614..956e57d4b 100644 --- a/packages/happy-dom/src/event/Event.ts +++ b/packages/happy-dom/src/event/Event.ts @@ -1,5 +1,4 @@ import IEventInit from './IEventInit'; -import EventTarget from './EventTarget'; import INode from '../nodes/node/INode'; import IWindow from '../window/IWindow'; import IShadowRoot from '../nodes/shadow-root/IShadowRoot'; @@ -10,8 +9,8 @@ import IEventTarget from './IEventTarget'; */ export default class Event { public composed = false; - public currentTarget: EventTarget = null; - public target: EventTarget = null; + public currentTarget: IEventTarget = null; + public target: IEventTarget = null; public bubbles = false; public cancelable = false; public defaultPrevented = false; diff --git a/packages/happy-dom/src/exception/DOMException.ts b/packages/happy-dom/src/exception/DOMException.ts index c6728be23..c68726997 100644 --- a/packages/happy-dom/src/exception/DOMException.ts +++ b/packages/happy-dom/src/exception/DOMException.ts @@ -1,3 +1,5 @@ +import DOMExceptionNameEnum from './DOMExceptionNameEnum'; + /** * DOM Exception. * @@ -14,8 +16,6 @@ export default class DOMException extends Error { constructor(message: string, name: string = null) { super(message); - if (name) { - this.name = name; - } + this.name = name || DOMExceptionNameEnum.domException; } } diff --git a/packages/happy-dom/src/exception/DOMExceptionNameEnum.ts b/packages/happy-dom/src/exception/DOMExceptionNameEnum.ts index c5e2b94ec..08aa5cb5c 100644 --- a/packages/happy-dom/src/exception/DOMExceptionNameEnum.ts +++ b/packages/happy-dom/src/exception/DOMExceptionNameEnum.ts @@ -7,6 +7,7 @@ enum DOMExceptionNameEnum { wrongDocumentError = 'WrongDocumentError', invalidNodeTypeError = 'InvalidNodeTypeError', invalidCharacterError = 'InvalidCharacterError', - notFoundError = 'NotFoundError' + notFoundError = 'NotFoundError', + domException = 'DOMException' } export default DOMExceptionNameEnum; diff --git a/packages/happy-dom/src/index.ts b/packages/happy-dom/src/index.ts index be83d4675..f6b3d0172 100644 --- a/packages/happy-dom/src/index.ts +++ b/packages/happy-dom/src/index.ts @@ -13,7 +13,7 @@ import File from './file/File'; import FileReader from './file/FileReader'; import DOMException from './exception/DOMException'; import History from './history/History'; -import CSSStyleDeclaration from './css/CSSStyleDeclaration'; +import CSSStyleDeclaration from './css/declaration/CSSStyleDeclaration'; import Screen from './screen/Screen'; import AsyncTaskManager from './async-task-manager/AsyncTaskManager'; import NodeFilter from './tree-walker/NodeFilter'; diff --git a/packages/happy-dom/src/attribute/Attr.ts b/packages/happy-dom/src/nodes/attr/Attr.ts similarity index 73% rename from packages/happy-dom/src/attribute/Attr.ts rename to packages/happy-dom/src/nodes/attr/Attr.ts index eea007c45..6a8814121 100644 --- a/packages/happy-dom/src/attribute/Attr.ts +++ b/packages/happy-dom/src/nodes/attr/Attr.ts @@ -1,12 +1,13 @@ -import IDocument from '../nodes/document/IDocument'; -import IElement from '../nodes/element/IElement'; +import IElement from '../element/IElement'; +import Node from '../node/Node'; +import IAttr from './IAttr'; /** * Attribute node interface. * * Reference: https://developer.mozilla.org/en-US/docs/Web/API/Attr. */ -export default class Attr { +export default class Attr extends Node implements IAttr { public value: string = null; public name: string = null; public namespaceURI: string = null; @@ -16,11 +17,6 @@ export default class Attr { */ public readonly ownerElement: IElement = null; - /** - * @deprecated - */ - public readonly ownerDocument: IDocument = null; - /** * @deprecated */ @@ -43,4 +39,11 @@ export default class Attr { public get prefix(): string { return this.name ? this.name.split(':')[0] : null; } + + /** + * @override + */ + public get textContent(): string { + return this.value; + } } diff --git a/packages/happy-dom/src/nodes/attr/IAttr.ts b/packages/happy-dom/src/nodes/attr/IAttr.ts new file mode 100644 index 000000000..aa6f01bc3 --- /dev/null +++ b/packages/happy-dom/src/nodes/attr/IAttr.ts @@ -0,0 +1,15 @@ +import IElement from '../element/IElement'; +import INode from './../node/INode'; + +/** + * Attr. + */ +export default interface IAttr extends INode { + value: string; + name: string; + namespaceURI: string; + readonly ownerElement: IElement; + readonly specified: boolean; + readonly localName: string; + readonly prefix: string; +} diff --git a/packages/happy-dom/src/nodes/child-node/ChildNodeUtility.ts b/packages/happy-dom/src/nodes/child-node/ChildNodeUtility.ts index 77bb8a386..5efb28624 100644 --- a/packages/happy-dom/src/nodes/child-node/ChildNodeUtility.ts +++ b/packages/happy-dom/src/nodes/child-node/ChildNodeUtility.ts @@ -1,3 +1,4 @@ +import DOMException from '../../exception/DOMException'; import XMLParser from '../../xml-parser/XMLParser'; import Document from '../document/Document'; import INode from '../node/INode'; @@ -29,7 +30,7 @@ export default class ChildNodeUtility { const parent = childNode.parentNode; if (!parent) { - return; + throw new DOMException('This element has no parent node.'); } for (const node of nodes) { diff --git a/packages/happy-dom/src/nodes/document/Document.ts b/packages/happy-dom/src/nodes/document/Document.ts index 43235f9ac..d3be75793 100644 --- a/packages/happy-dom/src/nodes/document/Document.ts +++ b/packages/happy-dom/src/nodes/document/Document.ts @@ -11,7 +11,7 @@ import Event from '../../event/Event'; import DOMImplementation from '../../dom-implementation/DOMImplementation'; import ElementTag from '../../config/ElementTag'; import INodeFilter from '../../tree-walker/INodeFilter'; -import Attr from '../../attribute/Attr'; +import Attr from '../attr/Attr'; import NamespaceURI from '../../config/NamespaceURI'; import DocumentType from '../document-type/DocumentType'; import ParentNodeUtility from '../parent-node/ParentNodeUtility'; @@ -40,6 +40,7 @@ import Selection from '../../selection/Selection'; import IShadowRoot from '../shadow-root/IShadowRoot'; import Range from '../../range/Range'; import IHTMLBaseElement from '../html-base-element/IHTMLBaseElement'; +import IAttr from '../attr/IAttr'; /** * Document. @@ -716,14 +717,11 @@ export default class Document extends Node implements IDocument { /** * Creates an Attr node. * - * @param name Name. + * @param qualifiedName Name. * @returns Attribute. */ - public createAttribute(name: string): Attr { - const attribute = new Attr(); - attribute.name = name.toLowerCase(); - (attribute.ownerDocument) = this; - return attribute; + public createAttribute(qualifiedName: string): IAttr { + return this.createAttributeNS(null, qualifiedName); } /** @@ -733,12 +731,12 @@ export default class Document extends Node implements IDocument { * @param qualifiedName Qualified name. * @returns Element. */ - public createAttributeNS(namespaceURI: string, qualifiedName: string): Attr { + public createAttributeNS(namespaceURI: string, qualifiedName: string): IAttr { + Attr._ownerDocument = this; const attribute = new Attr(); attribute.namespaceURI = namespaceURI; attribute.name = qualifiedName; - (attribute.ownerDocument) = this; - return attribute; + return attribute; } /** diff --git a/packages/happy-dom/src/nodes/document/IDocument.ts b/packages/happy-dom/src/nodes/document/IDocument.ts index a1967ec27..bb5f6059d 100644 --- a/packages/happy-dom/src/nodes/document/IDocument.ts +++ b/packages/happy-dom/src/nodes/document/IDocument.ts @@ -5,7 +5,7 @@ import TreeWalker from '../../tree-walker/TreeWalker'; import Event from '../../event/Event'; import DOMImplementation from '../../dom-implementation/DOMImplementation'; import INodeFilter from '../../tree-walker/INodeFilter'; -import Attr from '../../attribute/Attr'; +import IAttr from '../attr/IAttr'; import IDocumentType from '../document-type/IDocumentType'; import IParentNode from '../parent-node/IParentNode'; import INode from '../node/INode'; @@ -125,7 +125,7 @@ export default interface IDocument extends IParentNode { * @param name Name. * @returns Attribute. */ - createAttribute(name: string): Attr; + createAttribute(name: string): IAttr; /** * Creates a namespaced Attr node. @@ -134,7 +134,7 @@ export default interface IDocument extends IParentNode { * @param qualifiedName Qualified name. * @returns Element. */ - createAttributeNS(namespaceURI: string, qualifiedName: string): Attr; + createAttributeNS(namespaceURI: string, qualifiedName: string): IAttr; /** * Imports a node. diff --git a/packages/happy-dom/src/nodes/element/Element.ts b/packages/happy-dom/src/nodes/element/Element.ts index 203b3f249..7f8eed2bc 100644 --- a/packages/happy-dom/src/nodes/element/Element.ts +++ b/packages/happy-dom/src/nodes/element/Element.ts @@ -1,6 +1,6 @@ import Node from '../node/Node'; import ShadowRoot from '../shadow-root/ShadowRoot'; -import Attr from '../../attribute/Attr'; +import Attr from '../attr/Attr'; import DOMRect from './DOMRect'; import DOMTokenList from '../../dom-token-list/DOMTokenList'; import IDOMTokenList from '../../dom-token-list/IDOMTokenList'; @@ -26,6 +26,7 @@ import { TInsertAdjacentPositions } from './IElement'; import IText from '../text/IText'; import IDOMRectList from './IDOMRectList'; import DOMRectListFactory from './DOMRectListFactory'; +import IAttr from '../attr/IAttr'; /** * Element. @@ -46,7 +47,7 @@ export default class Element extends Node implements IElement { // Used for being able to access closed shadow roots public _shadowRoot: IShadowRoot = null; - public _attributes: { [k: string]: Attr } = {}; + public _attributes: { [k: string]: IAttr } = {}; private _classList: DOMTokenList = null; public _isValue?: string; @@ -211,7 +212,7 @@ export default class Element extends Node implements IElement { * * @returns Attributes. */ - public get attributes(): { [k: string]: Attr | number } { + public get attributes(): { [k: string]: IAttr | number } { const attributes = Object.values(this._attributes); return Object.assign({}, this._attributes, attributes, { length: attributes.length @@ -302,6 +303,8 @@ export default class Element extends Node implements IElement { public cloneNode(deep = false): IElement { const clone = super.cloneNode(deep); + Attr._ownerDocument = this.ownerDocument; + for (const key of Object.keys(this._attributes)) { const attr = Object.assign(new Attr(), this._attributes[key]); (attr.ownerElement) = clone; @@ -802,13 +805,13 @@ export default class Element extends Node implements IElement { * @param attribute Attribute. * @returns Replaced attribute. */ - public setAttributeNode(attribute: Attr): Attr { + public setAttributeNode(attribute: IAttr): IAttr { const name = this._getAttributeName(attribute.name); const replacedAttribute = this._attributes[name]; const oldValue = replacedAttribute ? replacedAttribute.value : null; attribute.name = name; - (attribute.ownerElement) = this; + (attribute.ownerElement) = this; (attribute.ownerDocument) = this.ownerDocument; this._attributes[name] = attribute; @@ -849,7 +852,7 @@ export default class Element extends Node implements IElement { * @param attribute Attribute. * @returns Replaced attribute. */ - public setAttributeNodeNS(attribute: Attr): Attr { + public setAttributeNodeNS(attribute: IAttr): IAttr { return this.setAttributeNode(attribute); } @@ -859,7 +862,7 @@ export default class Element extends Node implements IElement { * @param name Name. * @returns Replaced attribute. */ - public getAttributeNode(name: string): Attr { + public getAttributeNode(name: string): IAttr { return this._attributes[this._getAttributeName(name)] || null; } @@ -870,7 +873,7 @@ export default class Element extends Node implements IElement { * @param name Name. * @returns Replaced attribute. */ - public getAttributeNodeNS(namespace: string, name: string): Attr { + public getAttributeNodeNS(namespace: string, name: string): IAttr { const attributeName = this._getAttributeName(name); if ( this._attributes[attributeName] && @@ -893,7 +896,7 @@ export default class Element extends Node implements IElement { * * @param attribute Attribute. */ - public removeAttributeNode(attribute: Attr): void { + public removeAttributeNode(attribute: IAttr): void { delete this._attributes[attribute.name]; this._updateDomListIndices(); @@ -930,7 +933,7 @@ export default class Element extends Node implements IElement { * * @param attribute Attribute. */ - public removeAttributeNodeNS(attribute: Attr): void { + public removeAttributeNodeNS(attribute: IAttr): void { this.removeAttributeNode(attribute); } diff --git a/packages/happy-dom/src/nodes/element/IElement.ts b/packages/happy-dom/src/nodes/element/IElement.ts index 72c0cab6f..77206e4cc 100644 --- a/packages/happy-dom/src/nodes/element/IElement.ts +++ b/packages/happy-dom/src/nodes/element/IElement.ts @@ -1,5 +1,5 @@ import IShadowRoot from '../shadow-root/IShadowRoot'; -import Attr from '../../attribute/Attr'; +import IAttr from '../attr/IAttr'; import DOMRect from './DOMRect'; import IDOMTokenList from '../../dom-token-list/IDOMTokenList'; import INode from './../node/INode'; @@ -27,7 +27,7 @@ export default interface IElement extends IChildNode, INonDocumentTypeChildNode, slot: string; readonly nodeName: string; readonly localName: string; - readonly attributes: { [k: string]: Attr | number }; + readonly attributes: { [k: string]: IAttr | number }; /** * Attribute changed callback. @@ -190,7 +190,7 @@ export default interface IElement extends IChildNode, INonDocumentTypeChildNode, * @param attribute Attribute. * @returns Replaced attribute. */ - setAttributeNode(attribute: Attr): Attr; + setAttributeNode(attribute: IAttr): IAttr; /** * The setAttributeNodeNS() method adds a new Attr node to the specified element. @@ -198,7 +198,7 @@ export default interface IElement extends IChildNode, INonDocumentTypeChildNode, * @param attribute Attribute. * @returns Replaced attribute. */ - setAttributeNodeNS(attribute: Attr): Attr; + setAttributeNodeNS(attribute: IAttr): IAttr; /** * Returns an Attr node. @@ -206,7 +206,7 @@ export default interface IElement extends IChildNode, INonDocumentTypeChildNode, * @param name Name. * @returns Replaced attribute. */ - getAttributeNode(name: string): Attr; + getAttributeNode(name: string): IAttr; /** * Returns a namespaced Attr node. @@ -215,21 +215,21 @@ export default interface IElement extends IChildNode, INonDocumentTypeChildNode, * @param nodeName Node name. * @returns Replaced attribute. */ - getAttributeNodeNS(namespace: string, nodeName: string): Attr; + getAttributeNodeNS(namespace: string, nodeName: string): IAttr; /** * Removes an Attr node. * * @param attribute Attribute. */ - removeAttributeNode(attribute: Attr): void; + removeAttributeNode(attribute: IAttr): void; /** * Removes an Attr node. * * @param attribute Attribute. */ - removeAttributeNodeNS(attribute: Attr): void; + removeAttributeNodeNS(attribute: IAttr): void; /** * Clones a node. diff --git a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts index e7bc9eb4a..d71a21b0f 100644 --- a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts +++ b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts @@ -1,11 +1,12 @@ import Element from '../element/Element'; import IHTMLElement from './IHTMLElement'; -import CSSStyleDeclaration from '../../css/CSSStyleDeclaration'; -import Attr from '../../attribute/Attr'; +import CSSStyleDeclaration from '../../css/declaration/CSSStyleDeclaration'; +import IAttr from '../attr/IAttr'; import FocusEvent from '../../event/events/FocusEvent'; import PointerEvent from '../../event/events/PointerEvent'; -import Node from '../node/Node'; import DatasetUtility from './DatasetUtility'; +import NodeTypeEnum from '../node/NodeTypeEnum'; +import DOMException from '../../exception/DOMException'; /** * HTML Element. @@ -54,32 +55,95 @@ export default class HTMLElement extends Element implements IHTMLElement { /** * Returns inner text, which is the rendered appearance of text. * + * @see https://html.spec.whatwg.org/multipage/dom.html#the-innertext-idl-attribute * @returns Inner text. */ public get innerText(): string { + if (!this.isConnected) { + return this.textContent; + } + let result = ''; + for (const childNode of this.childNodes) { if (childNode instanceof HTMLElement) { if (childNode.tagName !== 'SCRIPT' && childNode.tagName !== 'STYLE') { result += childNode.innerText; } } else if ( - childNode.nodeType === Node.ELEMENT_NODE || - childNode.nodeType === Node.TEXT_NODE + childNode.nodeType === NodeTypeEnum.elementNode || + childNode.nodeType === NodeTypeEnum.textNode ) { - result += childNode.textContent; + result += childNode.textContent.replace(/[\n\r]/, ''); + } + + if (childNode.nodeType === NodeTypeEnum.elementNode) { + const computedStyle = this.ownerDocument.defaultView.getComputedStyle( + childNode + ); + if (computedStyle.display === 'block') { + result += '\n'; + } } } + return result; } /** * Sets the inner text, which is the rendered appearance of text. * + * @see https://html.spec.whatwg.org/multipage/dom.html#the-innertext-idl-attribute * @param innerText Inner text. */ - public set innerText(innerText: string) { - this.textContent = innerText; + public set innerText(text: string) { + for (const child of this.childNodes.slice()) { + this.removeChild(child); + } + + const texts = text.split(/[\n\r]/); + + for (let i = 0, max = texts.length; i < max; i++) { + if (i !== 0) { + this.appendChild(this.ownerDocument.createElement('br')); + } + this.appendChild(this.ownerDocument.createTextNode(texts[i])); + } + } + + /** + * Returns outer HTML. + * + * @see https://html.spec.whatwg.org/multipage/dom.html#the-innertext-idl-attribute + * @returns HTML. + */ + public get outerText(): string { + return this.innerText; + } + + /** + * Returns outer HTML. + * + * @see https://html.spec.whatwg.org/multipage/dom.html#the-innertext-idl-attribute + * @param text Text. + */ + public set outerText(text: string) { + if (!this.parentNode) { + throw new DOMException( + "Failed to set the 'outerHTML' property on 'Element': This element has no parent node." + ); + } + + const texts = text.split(/[\n\r]/); + + for (let i = 0, max = texts.length; i < max; i++) { + if (i !== 0) { + this.parentNode.insertBefore(this.ownerDocument.createElement('br'), this); + } + this.parentNode.insertBefore(this.ownerDocument.createTextNode(texts[i]), this); + } + + this.parentNode.removeChild(this); } /** @@ -89,7 +153,7 @@ export default class HTMLElement extends Element implements IHTMLElement { */ public get style(): CSSStyleDeclaration { if (!this._style) { - this._style = new CSSStyleDeclaration(this._attributes); + this._style = new CSSStyleDeclaration(this); } return this._style; } @@ -311,7 +375,7 @@ export default class HTMLElement extends Element implements IHTMLElement { * @param attribute Attribute. * @returns Replaced attribute. */ - public setAttributeNode(attribute: Attr): Attr { + public setAttributeNode(attribute: IAttr): IAttr { const replacedAttribute = super.setAttributeNode(attribute); if (attribute.name === 'style' && this._style) { @@ -327,7 +391,7 @@ export default class HTMLElement extends Element implements IHTMLElement { * @override * @param attribute Attribute. */ - public removeAttributeNode(attribute: Attr): void { + public removeAttributeNode(attribute: IAttr): void { super.removeAttributeNode(attribute); if (attribute.name === 'style' && this._style) { diff --git a/packages/happy-dom/src/nodes/html-element/IHTMLElement.ts b/packages/happy-dom/src/nodes/html-element/IHTMLElement.ts index a88fa6e46..db6810764 100644 --- a/packages/happy-dom/src/nodes/html-element/IHTMLElement.ts +++ b/packages/happy-dom/src/nodes/html-element/IHTMLElement.ts @@ -1,4 +1,4 @@ -import CSSStyleDeclaration from '../../css/CSSStyleDeclaration'; +import CSSStyleDeclaration from '../../css/declaration/CSSStyleDeclaration'; import IElement from '../element/IElement'; /** @@ -9,6 +9,7 @@ import IElement from '../element/IElement'; */ export default interface IHTMLElement extends IElement { style: CSSStyleDeclaration; + dataset: { [key: string]: string }; tabIndex: number; offsetHeight: number; offsetWidth: number; @@ -17,6 +18,7 @@ export default interface IHTMLElement extends IElement { clientHeight: number; clientWidth: number; innerText: string; + outerText: string; /** * Triggers a click event. diff --git a/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts b/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts index cd753d35a..22ccefadf 100644 --- a/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts +++ b/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts @@ -1,4 +1,4 @@ -import Attr from '../../attribute/Attr'; +import IAttr from '../attr/IAttr'; import CSSStyleSheet from '../../css/CSSStyleSheet'; import ResourceFetchHandler from '../../fetch/ResourceFetchHandler'; import HTMLElement from '../html-element/HTMLElement'; @@ -186,7 +186,7 @@ export default class HTMLLinkElement extends HTMLElement implements IHTMLLinkEle * @param attribute Attribute. * @returns Replaced attribute. */ - public setAttributeNode(attribute: Attr): Attr { + public setAttributeNode(attribute: IAttr): IAttr { const replacedAttribute = super.setAttributeNode(attribute); const rel = this.getAttributeNS(null, 'rel'); const href = this.getAttributeNS(null, 'href'); diff --git a/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElement.ts b/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElement.ts index b3191dfe7..8115610a0 100644 --- a/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElement.ts +++ b/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElement.ts @@ -1,4 +1,4 @@ -import Attr from '../../attribute/Attr'; +import IAttr from '../attr/IAttr'; import HTMLElement from '../html-element/HTMLElement'; import IHTMLScriptElement from './IHTMLScriptElement'; import ScriptUtility from './ScriptUtility'; @@ -158,7 +158,7 @@ export default class HTMLScriptElement extends HTMLElement implements IHTMLScrip * @param attribute Attribute. * @returns Replaced attribute. */ - public setAttributeNode(attribute: Attr): Attr { + public setAttributeNode(attribute: IAttr): IAttr { const replacedAttribute = super.setAttributeNode(attribute); if (attribute.name === 'src' && attribute.value !== null && this.isConnected) { diff --git a/packages/happy-dom/src/nodes/html-style-element/HTMLStyleElement.ts b/packages/happy-dom/src/nodes/html-style-element/HTMLStyleElement.ts index f0c74ff25..9a73d2fbf 100644 --- a/packages/happy-dom/src/nodes/html-style-element/HTMLStyleElement.ts +++ b/packages/happy-dom/src/nodes/html-style-element/HTMLStyleElement.ts @@ -22,7 +22,7 @@ export default class HTMLStyleElement extends HTMLElement implements IHTMLStyleE } if (!this._styleSheet) { this._styleSheet = new CSSStyleSheet(); - this._styleSheet.replaceSync(this.innerText); + this._styleSheet.replaceSync(this.textContent); } return this._styleSheet; } diff --git a/packages/happy-dom/src/nodes/node/INode.ts b/packages/happy-dom/src/nodes/node/INode.ts index 535ee71d4..54d4afbd2 100644 --- a/packages/happy-dom/src/nodes/node/INode.ts +++ b/packages/happy-dom/src/nodes/node/INode.ts @@ -6,7 +6,9 @@ import NodeTypeEnum from './NodeTypeEnum'; export default interface INode extends IEventTarget { readonly ELEMENT_NODE: NodeTypeEnum; + readonly ATTRIBUTE_NODE: NodeTypeEnum; readonly TEXT_NODE: NodeTypeEnum; + readonly CDATA_SECTION_NODE: NodeTypeEnum; readonly COMMENT_NODE: NodeTypeEnum; readonly DOCUMENT_NODE: NodeTypeEnum; readonly DOCUMENT_TYPE_NODE: NodeTypeEnum; diff --git a/packages/happy-dom/src/nodes/node/Node.ts b/packages/happy-dom/src/nodes/node/Node.ts index d4ea9f648..17bd330f3 100644 --- a/packages/happy-dom/src/nodes/node/Node.ts +++ b/packages/happy-dom/src/nodes/node/Node.ts @@ -21,14 +21,18 @@ export default class Node extends EventTarget implements INode { // Public properties public static readonly ELEMENT_NODE = NodeTypeEnum.elementNode; + public static readonly ATTRIBUTE_NODE = NodeTypeEnum.attributeNode; public static readonly TEXT_NODE = NodeTypeEnum.textNode; + public static readonly CDATA_SECTION_NODE = NodeTypeEnum.cdataSectionNode; public static readonly COMMENT_NODE = NodeTypeEnum.commentNode; public static readonly DOCUMENT_NODE = NodeTypeEnum.documentNode; public static readonly DOCUMENT_TYPE_NODE = NodeTypeEnum.documentTypeNode; public static readonly DOCUMENT_FRAGMENT_NODE = NodeTypeEnum.documentFragmentNode; public static readonly PROCESSING_INSTRUCTION_NODE = NodeTypeEnum.processingInstructionNode; public readonly ELEMENT_NODE = NodeTypeEnum.elementNode; + public readonly ATTRIBUTE_NODE = NodeTypeEnum.attributeNode; public readonly TEXT_NODE = NodeTypeEnum.textNode; + public readonly CDATA_SECTION_NODE = NodeTypeEnum.cdataSectionNode; public readonly COMMENT_NODE = NodeTypeEnum.commentNode; public readonly DOCUMENT_NODE = NodeTypeEnum.documentNode; public readonly DOCUMENT_TYPE_NODE = NodeTypeEnum.documentTypeNode; @@ -58,16 +62,18 @@ export default class Node extends EventTarget implements INode { * @returns Text content. */ public get textContent(): string { + // Sub-classes should implement this method. return null; } /** * Sets text content. * - * @param textContent Text content. + * @param _textContent Text content. */ public set textContent(_textContent) { // Do nothing. + // Sub-classes should implement this method. } /** diff --git a/packages/happy-dom/src/nodes/node/NodeTypeEnum.ts b/packages/happy-dom/src/nodes/node/NodeTypeEnum.ts index 40e856e5c..29d650f26 100644 --- a/packages/happy-dom/src/nodes/node/NodeTypeEnum.ts +++ b/packages/happy-dom/src/nodes/node/NodeTypeEnum.ts @@ -1,6 +1,8 @@ enum NodeTypeEnum { elementNode = 1, + attributeNode = 2, textNode = 3, + cdataSectionNode = 4, commentNode = 8, documentNode = 9, documentTypeNode = 10, diff --git a/packages/happy-dom/src/nodes/svg-element/ISVGElement.ts b/packages/happy-dom/src/nodes/svg-element/ISVGElement.ts index 27a194859..797e98828 100644 --- a/packages/happy-dom/src/nodes/svg-element/ISVGElement.ts +++ b/packages/happy-dom/src/nodes/svg-element/ISVGElement.ts @@ -1,4 +1,4 @@ -import CSSStyleDeclaration from '../../css/CSSStyleDeclaration'; +import CSSStyleDeclaration from '../../css/declaration/CSSStyleDeclaration'; import IElement from '../element/IElement'; import ISVGSVGElement from './ISVGSVGElement'; diff --git a/packages/happy-dom/src/nodes/svg-element/SVGElement.ts b/packages/happy-dom/src/nodes/svg-element/SVGElement.ts index 483beb646..d2c904a5e 100644 --- a/packages/happy-dom/src/nodes/svg-element/SVGElement.ts +++ b/packages/happy-dom/src/nodes/svg-element/SVGElement.ts @@ -1,8 +1,8 @@ -import CSSStyleDeclaration from '../../css/CSSStyleDeclaration'; +import CSSStyleDeclaration from '../../css/declaration/CSSStyleDeclaration'; import Element from '../element/Element'; import ISVGElement from './ISVGElement'; import ISVGSVGElement from './ISVGSVGElement'; -import Attr from '../../attribute/Attr'; +import IAttr from '../attr/IAttr'; /** * SVG Element. @@ -59,7 +59,7 @@ export default class SVGElement extends Element implements ISVGElement { */ public get style(): CSSStyleDeclaration { if (!this._style) { - this._style = new CSSStyleDeclaration(this._attributes); + this._style = new CSSStyleDeclaration(this); } return this._style; } @@ -71,7 +71,7 @@ export default class SVGElement extends Element implements ISVGElement { * @param attribute Attribute. * @returns Replaced attribute. */ - public setAttributeNode(attribute: Attr): Attr { + public setAttributeNode(attribute: IAttr): IAttr { const replacedAttribute = super.setAttributeNode(attribute); if (attribute.name === 'style' && this._style) { @@ -87,7 +87,7 @@ export default class SVGElement extends Element implements ISVGElement { * @override * @param attribute Attribute. */ - public removeAttributeNode(attribute: Attr): void { + public removeAttributeNode(attribute: IAttr): void { super.removeAttributeNode(attribute); if (attribute.name === 'style' && this._style) { diff --git a/packages/happy-dom/src/window/IWindow.ts b/packages/happy-dom/src/window/IWindow.ts index b2d23151f..56cb8302e 100644 --- a/packages/happy-dom/src/window/IWindow.ts +++ b/packages/happy-dom/src/window/IWindow.ts @@ -48,7 +48,7 @@ import DOMException from '../exception/DOMException'; import FileReader from '../file/FileReader'; import History from '../history/History'; import CSSStyleSheet from '../css/CSSStyleSheet'; -import CSSStyleDeclaration from '../css/CSSStyleDeclaration'; +import CSSStyleDeclaration from '../css/declaration/CSSStyleDeclaration'; import CSS from '../css/CSS'; import CSSUnitValue from '../css/CSSUnitValue'; import PointerEvent from '../event/events/PointerEvent'; @@ -84,6 +84,7 @@ import Range from '../range/Range'; import MediaQueryList from '../match-media/MediaQueryList'; import DOMRect from '../nodes/element/DOMRect'; import Window from './Window'; +import Attr from '../nodes/attr/Attr'; import { URLSearchParams } from 'url'; import { Performance } from 'perf_hooks'; @@ -115,6 +116,7 @@ export default interface IWindow extends IEventTarget, NodeJS.Global { readonly HTMLMetaElement: typeof HTMLMetaElement; readonly HTMLBaseElement: typeof HTMLBaseElement; readonly HTMLDialogElement: typeof HTMLDialogElement; + readonly Attr: typeof Attr; readonly SVGSVGElement: typeof SVGSVGElement; readonly SVGElement: typeof SVGElement; readonly Image: typeof Image; diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts index 95ede0e06..c4182a1dc 100644 --- a/packages/happy-dom/src/window/Window.ts +++ b/packages/happy-dom/src/window/Window.ts @@ -50,7 +50,7 @@ import DOMException from '../exception/DOMException'; import { default as FileReaderImplementation } from '../file/FileReader'; import History from '../history/History'; import CSSStyleSheet from '../css/CSSStyleSheet'; -import CSSStyleDeclaration from '../css/CSSStyleDeclaration'; +import CSSStyleDeclaration from '../css/declaration/CSSStyleDeclaration'; import CSS from '../css/CSS'; import CSSUnitValue from '../css/CSSUnitValue'; import MouseEvent from '../event/events/MouseEvent'; @@ -96,6 +96,9 @@ import VM from 'vm'; import { Buffer } from 'buffer'; import Base64 from '../base64/Base64'; import IDocument from '../nodes/document/IDocument'; +import Attr from '../nodes/attr/Attr'; +import ComputedStyle from '../css/computed-style/ComputedStyle'; +import IElement from '../nodes/element/IElement'; const ORIGINAL_SET_TIMEOUT = setTimeout; const ORIGINAL_CLEAR_TIMEOUT = clearTimeout; @@ -137,6 +140,7 @@ export default class Window extends EventTarget implements IWindow { public readonly HTMLMetaElement = HTMLMetaElement; public readonly HTMLBaseElement = HTMLBaseElement; public readonly HTMLDialogElement = HTMLDialogElement; + public readonly Attr = Attr; public readonly SVGSVGElement = SVGSVGElement; public readonly SVGElement = SVGElement; public readonly Text = Text; @@ -418,8 +422,8 @@ export default class Window extends EventTarget implements IWindow { * @param element Element. * @returns CSS style declaration. */ - public getComputedStyle(element: HTMLElement): CSSStyleDeclaration { - return new CSSStyleDeclaration(element._attributes, element); + public getComputedStyle(element: IElement): CSSStyleDeclaration { + return ComputedStyle.getComputedStyle(element); } /** diff --git a/packages/happy-dom/test/css/CSSStyleDeclaration.test.ts b/packages/happy-dom/test/css/CSSStyleDeclaration.test.ts deleted file mode 100644 index ee5a7294e..000000000 --- a/packages/happy-dom/test/css/CSSStyleDeclaration.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -import CSSStyleDeclaration from '../../src/css/CSSStyleDeclaration'; -import CSSStyleDeclarationStyleProperties from './data/CSSStyleDeclarationStyleProperties'; -import Attr from '../../src/attribute/Attr'; - -function CAMEL_TO_KEBAB_CASE(string): string { - return string.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase(); -} - -describe('CSSStyleDeclaration', () => { - let attributes: { [k: string]: Attr } = null; - let cssStyleDeclaration: CSSStyleDeclaration = null; - - beforeEach(() => { - attributes = { style: new Attr() }; - cssStyleDeclaration = new CSSStyleDeclaration(attributes); - }); - - for (const property of CSSStyleDeclarationStyleProperties) { - describe(`get ${property}()`, () => { - it('Returns style property.', () => { - attributes.style.value = `${CAMEL_TO_KEBAB_CASE(property)}: test;`; - expect(cssStyleDeclaration[property]).toBe('test'); - }); - }); - - describe(`set ${property}()`, () => { - it('Sets style property.', () => { - cssStyleDeclaration[property] = 'test'; - expect(attributes.style.value).toBe(`${CAMEL_TO_KEBAB_CASE(property)}: test;`); - }); - }); - } - - describe('get cssText()', () => { - it('Returns CSS text.', () => { - attributes['style'] = new Attr(); - attributes['style'].name = 'style'; - attributes['style'].value = 'background: red; color: blue;'; - - expect(cssStyleDeclaration.cssText).toBe('background: red; color: blue;'); - }); - }); - - describe('set cssText()', () => { - it('Sets CSS text.', () => { - cssStyleDeclaration.cssText = 'line-height: 2; font-size: 12px; background'; - - // Test if setProperty() crashes when a single property is used in the style - cssStyleDeclaration.lineHeight = '3'; - - expect(cssStyleDeclaration.lineHeight).toBe('3'); - expect(cssStyleDeclaration.fontSize).toBe('12px'); - expect(cssStyleDeclaration.background).toBe(''); - expect(attributes['style'].value).toBe('line-height: 3; font-size: 12px; background;'); - }); - }); - - describe('item()', () => { - it('Returns an item by index.', () => { - cssStyleDeclaration.setProperty('background-color', 'green'); - expect(cssStyleDeclaration.item(0)).toBe('background-color'); - }); - }); - - describe('setProperty()', () => { - it('Sets a style property.', () => { - cssStyleDeclaration.setProperty('background-color', 'green'); - expect(attributes.style.value).toBe('background-color: green;'); - expect(cssStyleDeclaration.backgroundColor).toBe('green'); - expect(cssStyleDeclaration.length).toBe(1); - expect(cssStyleDeclaration[0]).toBe('background-color'); - }); - }); - - describe('removeProperty()', () => { - it('Removes a style property.', () => { - cssStyleDeclaration.setProperty('background-color', 'green'); - cssStyleDeclaration.removeProperty('background-color'); - expect(attributes.style).toBe(undefined); - expect(cssStyleDeclaration.backgroundColor).toBe(''); - expect(cssStyleDeclaration.length).toBe(0); - }); - }); - - describe('getPropertyValue()', () => { - it('Returns a style property.', () => { - cssStyleDeclaration.setProperty('background-color', 'green'); - expect(cssStyleDeclaration.getPropertyValue('background-color')).toBe('green'); - }); - }); -}); diff --git a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts new file mode 100644 index 000000000..b683ddba7 --- /dev/null +++ b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts @@ -0,0 +1,270 @@ +import CSSStyleDeclarationCamelCaseKeys from './data/CSSStyleDeclarationCamelCaseKeys'; +import CSSStyleDeclaration from '../../../src/css/declaration/CSSStyleDeclaration'; +import Window from '../../../src/window/Window'; +import IWindow from '../../../src/window/IWindow'; +import IDocument from '../../../src/nodes/document/IDocument'; +import IElement from '../../../src/nodes/element/IElement'; + +function CAMEL_TO_KEBAB_CASE(string): string { + return string.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase(); +} + +describe('CSSStyleDeclaration', () => { + let window: IWindow; + let document: IDocument; + let element: IElement; + + beforeEach(() => { + window = new Window(); + document = window.document; + element = document.createElement('div'); + }); + + describe(`get {number}()`, () => { + it('Returns name of property when style is set on element.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', `border: 2px solid green;border-radius: 2px;font-size: 12px;`); + + expect(declaration[0]).toBe('border'); + expect(declaration[1]).toBe('border-radius'); + expect(declaration[2]).toBe('border-style'); + expect(declaration[3]).toBe(undefined); + }); + + it('Returns name of property without element.', () => { + const declaration = new CSSStyleDeclaration(); + + declaration.border = '2px solid green'; + declaration.borderRadius = '2px'; + declaration.fontSize = '12px'; + + expect(declaration[0]).toBe('border'); + expect(declaration[1]).toBe('border-radius'); + expect(declaration[2]).toBe('border-style'); + expect(declaration[3]).toBe(undefined); + }); + }); + + for (const property of CSSStyleDeclarationCamelCaseKeys) { + describe(`get ${property}()`, () => { + it('Returns style property on element.', () => { + const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', `${property}: test;`); + expect(declaration[property]).toBe('test'); + }); + + it('Returns style property without element.', () => { + const declaration = new CSSStyleDeclaration(); + declaration[property] = 'test'; + expect(declaration[property]).toBe('test'); + }); + }); + + describe(`set ${property}()`, () => { + it('Sets style property on element.', () => { + const declaration = new CSSStyleDeclaration(element); + declaration[property] = 'test'; + expect(element.getAttribute('style')).toBe(`${CAMEL_TO_KEBAB_CASE(property)}: test;`); + }); + + it('Sets style property without element.', () => { + const declaration = new CSSStyleDeclaration(element); + declaration[property] = 'test'; + expect(declaration[property]).toBe('test'); + }); + }); + } + + describe('get length()', () => { + it('Returns length when of styles on element.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', `border: 2px solid green;border-radius: 2px;font-size: 12px;`); + + expect(declaration.length).toBe(3); + }); + + it('Returns length without element.', () => { + const declaration = new CSSStyleDeclaration(); + + declaration.border = '2px solid green'; + declaration.borderRadius = '2px'; + declaration.fontSize = '12px'; + + expect(declaration.length).toBe(3); + }); + }); + + describe('get cssText()', () => { + it('Returns CSS text when using element.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute( + 'style', + `border: 2px solid green; border-radius: 2px;font-size: 12px;` + ); + + expect(declaration.cssText).toBe( + 'border: 2px solid green; border-radius: 2px; font-size: 12px;' + ); + }); + + it('Returns CSS without element.', () => { + const declaration = new CSSStyleDeclaration(); + + declaration.border = '2px solid green'; + declaration.borderRadius = '2px'; + declaration.fontSize = '12px'; + + expect(declaration.cssText).toBe( + 'border: 2px solid green; border-radius: 2px; font-size: 12px;' + ); + }); + }); + + describe('set cssText()', () => { + it('Sets CSS text when using element.', () => { + const declaration = new CSSStyleDeclaration(element); + + declaration.cssText = 'border: 2px solid green; border-radius: 2px;font-size: 12px;'; + + expect(element.getAttribute('style')).toBe( + 'border: 2px solid green; border-radius: 2px; font-size: 12px;' + ); + }); + + it('Sets CSS text without element.', () => { + const declaration = new CSSStyleDeclaration(); + + declaration.cssText = 'border: 2px solid green; border-radius: 2px;font-size: 12px;'; + + expect(declaration.border).toBe('2px solid green'); + expect(declaration.borderRadius).toBe('2px'); + expect(declaration.fontSize).toBe('12px'); + }); + + it('Removes style property on element if empty value is sent.', () => { + const declaration = new CSSStyleDeclaration(element); + + declaration.cssText = ''; + + expect(element.getAttribute('style')).toBe(null); + }); + }); + + describe('item()', () => { + it('Returns an item by index when using element.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', `border: 2px solid green;border-radius: 2px;font-size: 12px;`); + + expect(declaration.item(0)).toBe('border'); + expect(declaration.item(1)).toBe('border-radius'); + expect(declaration.item(2)).toBe('font-size'); + expect(declaration.item(3)).toBe(''); + }); + + it('Returns an item by index without element.', () => { + const declaration = new CSSStyleDeclaration(element); + + declaration.cssText = 'border: 2px solid green;border-radius: 2px;font-size: 12px;'; + + expect(declaration.item(0)).toBe('border'); + expect(declaration.item(1)).toBe('border-radius'); + expect(declaration.item(2)).toBe('font-size'); + expect(declaration.item(3)).toBe(''); + }); + }); + + describe('setProperty()', () => { + it('Sets a CSS property when using element.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', `border: 2px solid green;border-radius: 2px;font-size: 12px;`); + + declaration.setProperty('background-color', 'green'); + declaration.setProperty('border-radius', '4px', 'important'); + + expect(element.getAttribute('style')).toBe( + 'border: 2px solid green; border-radius: 4px !important; font-size: 12px; background-color: green;' + ); + }); + + it('Sets a CSS property without element.', () => { + const declaration = new CSSStyleDeclaration(); + + declaration.cssText = `border: 2px solid green;border-radius: 2px;font-size: 12px;`; + declaration.setProperty('background-color', 'green'); + + expect(declaration.cssText).toBe( + 'border: 2px solid green; border-radius: 2px; font-size: 12px; background-color: green;' + ); + }); + + it('Removes style attribute on element if empty value is sent', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', `border: 2px solid green;`); + + declaration.setProperty('border', ''); + + expect(element.getAttribute('style')).toBe(null); + }); + }); + + describe('removeProperty()', () => { + it('Removes a CSS property when using element.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', `border: 2px solid green;border-radius: 2px;font-size: 12px;`); + declaration.removeProperty('border-radius'); + + expect(element.getAttribute('style')).toBe('border: 2px solid green; font-size: 12px;'); + }); + + it('Removes a CSS property without element.', () => { + const declaration = new CSSStyleDeclaration(); + + declaration.cssText = `border: 2px solid green;border-radius: 2px;font-size: 12px;`; + declaration.removeProperty('border-radius'); + + expect(declaration.cssText).toBe('border: 2px solid green; font-size: 12px;'); + }); + + it('Removes style attribute on element if there are no CSS properties left.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', `border: 2px solid green;border-radius: 2px;`); + + declaration.removeProperty('border'); + declaration.removeProperty('border-radius'); + + expect(element.getAttribute('style')).toBe(null); + }); + }); + + describe('getPropertyValue()', () => { + it('Returns a CSS property value when using element.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', `border: 2px solid green;border-radius: 2px;font-size: 12px;`); + + expect(declaration.getPropertyValue('border')).toBe('2px solid green'); + expect(declaration.getPropertyValue('border-radius')).toBe('2px'); + expect(declaration.getPropertyValue('font-size')).toBe('12px'); + expect(declaration.getPropertyValue('background')).toBe(''); + }); + + it('Returns a CSS property without element.', () => { + const declaration = new CSSStyleDeclaration(); + + declaration.cssText = `border: 2px solid green;border-radius: 2px;font-size: 12px;`; + + expect(declaration.getPropertyValue('border')).toBe('2px solid green'); + expect(declaration.getPropertyValue('border-radius')).toBe('2px'); + expect(declaration.getPropertyValue('font-size')).toBe('12px'); + expect(declaration.getPropertyValue('background')).toBe(''); + }); + }); +}); diff --git a/packages/happy-dom/test/css/data/CSSStyleDeclarationStyleProperties.ts b/packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationCamelCaseKeys.ts similarity index 100% rename from packages/happy-dom/test/css/data/CSSStyleDeclarationStyleProperties.ts rename to packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationCamelCaseKeys.ts diff --git a/packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationKebabCaseKeys.ts b/packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationKebabCaseKeys.ts new file mode 100644 index 000000000..c6fa9f9c0 --- /dev/null +++ b/packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationKebabCaseKeys.ts @@ -0,0 +1,371 @@ +export default [ + 'align-content', + 'align-items', + 'align-self', + 'alignment-baseline', + 'all', + 'animation', + 'animation-delay', + 'animation-direction', + 'animation-duration', + 'animation-fill-mode', + 'animation-iteration-count', + 'animation-name', + 'animation-play-state', + 'animation-timing-function', + 'appearance', + 'backdrop-filter', + 'backface-visibility', + 'background', + 'background-attachment', + 'background-blend-mode', + 'background-clip', + 'background-color', + 'background-image', + 'background-origin', + 'background-position', + 'background-position-x', + 'background-position-y', + 'background-repeat', + 'background-repeat-x', + 'background-repeat-y', + 'background-size', + 'baseline-shift', + 'block-size', + 'border', + 'border-block-end', + 'border-block-end-color', + 'border-block-end-style', + 'border-block-end-width', + 'border-block-start', + 'border-block-start-color', + 'border-block-start-style', + 'border-block-start-width', + 'border-bottom', + 'border-bottom-color', + 'border-bottom-left-radius', + 'border-bottom-right-radius', + 'border-bottom-style', + 'border-bottom-width', + 'border-collapse', + 'border-color', + 'border-image', + 'border-image-outset', + 'border-image-repeat', + 'border-image-slice', + 'border-image-source', + 'border-image-width', + 'border-inline-end', + 'border-inline-end-color', + 'border-inline-end-style', + 'border-inline-end-width', + 'border-inline-start', + 'border-inline-start-color', + 'border-inline-start-style', + 'border-inline-start-width', + 'border-left', + 'border-left-color', + 'border-left-style', + 'border-left-width', + 'border-radius', + 'border-right', + 'border-right-color', + 'border-right-style', + 'border-right-width', + 'border-spacing', + 'border-style', + 'border-top', + 'border-top-color', + 'border-top-left-radius', + 'border-top-right-radius', + 'border-top-style', + 'border-top-width', + 'border-width', + 'bottom', + 'box-shadow', + 'box-sizing', + 'break-after', + 'break-before', + 'break-inside', + 'buffered-rendering', + 'caption-side', + 'caret-color', + 'clear', + 'clip', + 'clip-path', + 'clip-rule', + 'color', + 'color-interpolation', + 'color-interpolation-filters', + 'color-rendering', + 'color-scheme', + 'column-count', + 'column-fill', + 'column-gap', + 'column-rule', + 'column-rule-color', + 'column-rule-style', + 'column-rule-width', + 'column-span', + 'column-width', + 'columns', + 'contain', + 'contain-intrinsic-size', + 'content', + 'content-visibility', + 'counter-increment', + 'counter-reset', + 'counter-set', + 'css-float', + 'cursor', + 'cx', + 'cy', + 'd', + 'direction', + 'display', + 'dominant-baseline', + 'empty-cells', + 'fill', + 'fill-opacity', + 'fill-rule', + 'filter', + 'flex', + 'flex-basis', + 'flex-direction', + 'flex-flow', + 'flex-grow', + 'flex-shrink', + 'flex-wrap', + 'float', + 'flood-color', + 'flood-opacity', + 'font', + 'font-display', + 'font-family', + 'font-feature-settings', + 'font-kerning', + 'font-optical-sizing', + 'font-size', + 'font-stretch', + 'font-style', + 'font-variant', + 'font-variant-caps', + 'font-variant-east-asian', + 'font-variant-ligatures', + 'font-variant-numeric', + 'font-variation-settings', + 'font-weight', + 'gap', + 'grid', + 'grid-area', + 'grid-auto-columns', + 'grid-auto-flow', + 'grid-auto-rows', + 'grid-column', + 'grid-column-end', + 'grid-column-gap', + 'grid-column-start', + 'grid-gap', + 'grid-row', + 'grid-row-end', + 'grid-row-gap', + 'grid-row-start', + 'grid-template', + 'grid-template-areas', + 'grid-template-columns', + 'grid-template-rows', + 'height', + 'hyphens', + 'image-orientation', + 'image-rendering', + 'inherits', + 'initial-value', + 'inline-size', + 'isolation', + 'justify-content', + 'justify-items', + 'justify-self', + 'left', + 'letter-spacing', + 'lighting-color', + 'line-break', + 'line-height', + 'list-style', + 'list-style-image', + 'list-style-position', + 'list-style-type', + 'margin', + 'margin-block-end', + 'margin-block-start', + 'margin-bottom', + 'margin-inline-end', + 'margin-inline-start', + 'margin-left', + 'margin-right', + 'margin-top', + 'marker', + 'marker-end', + 'marker-mid', + 'marker-start', + 'mask', + 'mask-type', + 'max-block-size', + 'max-height', + 'max-inline-size', + 'max-width', + 'max-zoom', + 'min-block-size', + 'min-height', + 'min-inline-size', + 'min-width', + 'min-zoom', + 'mix-blend-mode', + 'object-fit', + 'object-position', + 'offset', + 'offset-distance', + 'offset-path', + 'offset-rotate', + 'opacity', + 'order', + 'orientation', + 'orphans', + 'outline', + 'outline-color', + 'outline-offset', + 'outline-style', + 'outline-width', + 'overflow', + 'overflow-anchor', + 'overflow-wrap', + 'overflow-x', + 'overflow-y', + 'overscroll-behavior', + 'overscroll-behavior-block', + 'overscroll-behavior-inline', + 'overscroll-behavior-x', + 'overscroll-behavior-y', + 'padding', + 'padding-block-end', + 'padding-block-start', + 'padding-bottom', + 'padding-inline-end', + 'padding-inline-start', + 'padding-left', + 'padding-right', + 'padding-top', + 'page', + 'page-break-after', + 'page-break-before', + 'page-break-inside', + 'page-orientation', + 'paint-order', + 'perspective', + 'perspective-origin', + 'place-content', + 'place-items', + 'place-self', + 'pointer-events', + 'position', + 'quotes', + 'r', + 'resize', + 'right', + 'row-gap', + 'ruby-position', + 'rx', + 'ry', + 'scroll-behavior', + 'scroll-margin', + 'scroll-margin-block', + 'scroll-margin-block-end', + 'scroll-margin-block-start', + 'scroll-margin-bottom', + 'scroll-margin-inline', + 'scroll-margin-inline-end', + 'scroll-margin-inline-start', + 'scroll-margin-left', + 'scroll-margin-right', + 'scroll-margin-top', + 'scroll-padding', + 'scroll-padding-block', + 'scroll-padding-block-end', + 'scroll-padding-block-start', + 'scroll-padding-bottom', + 'scroll-padding-inline', + 'scroll-padding-inline-end', + 'scroll-padding-inline-start', + 'scroll-padding-left', + 'scroll-padding-right', + 'scroll-padding-top', + 'scroll-snap-align', + 'scroll-snap-stop', + 'scroll-snap-type', + 'shape-image-threshold', + 'shape-margin', + 'shape-outside', + 'shape-rendering', + 'size', + 'speak', + 'src', + 'stop-color', + 'stop-opacity', + 'stroke', + 'stroke-dasharray', + 'stroke-dashoffset', + 'stroke-linecap', + 'stroke-linejoin', + 'stroke-miterlimit', + 'stroke-opacity', + 'stroke-width', + 'syntax', + 'tab-size', + 'table-layout', + 'text-align', + 'text-align-last', + 'text-anchor', + 'text-combine-upright', + 'text-decoration', + 'text-decoration-color', + 'text-decoration-line', + 'text-decoration-skip-ink', + 'text-decoration-style', + 'text-indent', + 'text-orientation', + 'text-overflow', + 'text-rendering', + 'text-shadow', + 'text-size-adjust', + 'text-transform', + 'text-underline-position', + 'top', + 'touch-action', + 'transform', + 'transform-box', + 'transform-origin', + 'transform-style', + 'transition', + 'transition-delay', + 'transition-duration', + 'transition-property', + 'transition-timing-function', + 'unicode-bidi', + 'unicode-range', + 'user-select', + 'user-zoom', + 'vector-effect', + 'vertical-align', + 'visibility', + 'white-space', + 'widows', + 'width', + 'will-change', + 'word-break', + 'word-spacing', + 'word-wrap', + 'writing-mode', + 'x', + 'y', + 'z-index', + 'zoom' +]; diff --git a/packages/happy-dom/test/nodes/document/Document.test.ts b/packages/happy-dom/test/nodes/document/Document.test.ts index 02c151f2a..78c016d9e 100644 --- a/packages/happy-dom/test/nodes/document/Document.test.ts +++ b/packages/happy-dom/test/nodes/document/Document.test.ts @@ -12,7 +12,7 @@ import Element from '../../../src/nodes/element/Element'; import Event from '../../../src/event/Event'; import SVGSVGElement from '../../../src/nodes/svg-element/SVGSVGElement'; import NamespaceURI from '../../../src/config/NamespaceURI'; -import Attr from '../../../src/attribute/Attr'; +import Attr from '../../../src/nodes/attr/Attr'; import ParentNodeUtility from '../../../src/nodes/parent-node/ParentNodeUtility'; import QuerySelector from '../../../src/query-selector/QuerySelector'; import NodeFilter from '../../../src/tree-walker/NodeFilter'; diff --git a/packages/happy-dom/test/nodes/element/Element.test.ts b/packages/happy-dom/test/nodes/element/Element.test.ts index 2779c2e21..3f6a906bc 100644 --- a/packages/happy-dom/test/nodes/element/Element.test.ts +++ b/packages/happy-dom/test/nodes/element/Element.test.ts @@ -17,6 +17,7 @@ import Node from '../../../src/nodes/node/Node'; import IHTMLCollection from '../../../src/nodes/element/IHTMLCollection'; import IElement from '../../../src/nodes/element/IElement'; import INodeList from '../../../src/nodes/node/INodeList'; +import IAttr from '../../../src/nodes/attr/IAttr'; const NAMESPACE_URI = 'https://test.test'; @@ -1301,6 +1302,36 @@ describe('Element', () => { element[method](attribute1); element[method](attribute2); + expect((element.attributes[0]).name).toBe('key1'); + expect((element.attributes[0]).namespaceURI).toBe(NamespaceURI.svg); + expect((element.attributes[0]).value).toBe('value1'); + expect((element.attributes[0]).specified).toBe(true); + expect((element.attributes[0]).ownerElement).toBe(element); + expect((element.attributes[0]).ownerDocument).toBe(document); + + expect((element.attributes[1]).name).toBe('key2'); + expect((element.attributes[1]).namespaceURI).toBe(null); + expect((element.attributes[1]).value).toBe('value2'); + expect((element.attributes[1]).specified).toBe(true); + expect((element.attributes[1]).ownerElement).toBe(element); + expect((element.attributes[1]).ownerDocument).toBe(document); + + expect((element.attributes.key1).name).toBe('key1'); + expect((element.attributes.key1).namespaceURI).toBe(NamespaceURI.svg); + expect((element.attributes.key1).value).toBe('value1'); + expect((element.attributes.key1).specified).toBe(true); + expect((element.attributes.key1).ownerElement).toBe(element); + expect((element.attributes.key1).ownerDocument).toBe(document); + + expect((element.attributes.key2).name).toBe('key2'); + expect((element.attributes.key2).namespaceURI).toBe(null); + expect((element.attributes.key2).value).toBe('value2'); + expect((element.attributes.key2).specified).toBe(true); + expect((element.attributes.key2).ownerElement).toBe(element); + expect((element.attributes.key2).ownerDocument).toBe(document); + + expect(element.attributes.length).toBe(2); + expect(element.attributes).toEqual({ '0': { name: 'key1', @@ -1349,41 +1380,35 @@ describe('Element', () => { svg[method](attribute1); svg[method](attribute2); - expect(svg.attributes).toEqual({ - '0': { - name: 'KEY1', - namespaceURI: NamespaceURI.svg, - value: 'value1', - specified: true, - ownerElement: svg, - ownerDocument: document - }, - '1': { - name: 'key2', - namespaceURI: null, - value: 'value2', - specified: true, - ownerElement: svg, - ownerDocument: document - }, - KEY1: { - name: 'KEY1', - namespaceURI: NamespaceURI.svg, - value: 'value1', - specified: true, - ownerElement: svg, - ownerDocument: document - }, - key2: { - name: 'key2', - namespaceURI: null, - value: 'value2', - specified: true, - ownerElement: svg, - ownerDocument: document - }, - length: 2 - }); + expect((svg.attributes[0]).name).toBe('KEY1'); + expect((svg.attributes[0]).namespaceURI).toBe(NamespaceURI.svg); + expect((svg.attributes[0]).value).toBe('value1'); + expect((svg.attributes[0]).specified).toBe(true); + expect((svg.attributes[0]).ownerElement).toBe(svg); + expect((svg.attributes[0]).ownerDocument).toBe(document); + + expect((svg.attributes[1]).name).toBe('key2'); + expect((svg.attributes[1]).namespaceURI).toBe(null); + expect((svg.attributes[1]).value).toBe('value2'); + expect((svg.attributes[1]).specified).toBe(true); + expect((svg.attributes[1]).ownerElement).toBe(svg); + expect((svg.attributes[1]).ownerDocument).toBe(document); + + expect((svg.attributes.KEY1).name).toBe('KEY1'); + expect((svg.attributes.KEY1).namespaceURI).toBe(NamespaceURI.svg); + expect((svg.attributes.KEY1).value).toBe('value1'); + expect((svg.attributes.KEY1).specified).toBe(true); + expect((svg.attributes.KEY1).ownerElement).toBe(svg); + expect((svg.attributes.KEY1).ownerDocument).toBe(document); + + expect((svg.attributes.key2).name).toBe('key2'); + expect((svg.attributes.key2).namespaceURI).toBe(null); + expect((svg.attributes.key2).value).toBe('value2'); + expect((svg.attributes.key2).specified).toBe(true); + expect((svg.attributes.key2).ownerElement).toBe(svg); + expect((svg.attributes.key2).ownerDocument).toBe(document); + + expect(svg.attributes.length).toBe(2); }); }); } diff --git a/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts b/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts index cd6e7d620..a85655ae7 100644 --- a/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts +++ b/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts @@ -1,17 +1,19 @@ import PointerEvent from '../../../src/event/events/PointerEvent'; -import Document from '../../../src/nodes/document/Document'; +import IDocument from '../../../src/nodes/document/IDocument'; import HTMLElement from '../../../src/nodes/html-element/HTMLElement'; +import IHTMLElement from '../../../src/nodes/html-element/IHTMLElement'; +import IWindow from '../../../src/window/IWindow'; import Window from '../../../src/window/Window'; describe('HTMLElement', () => { - let window: Window = null; - let document: Document = null; - let element: HTMLElement = null; + let window: IWindow = null; + let document: IDocument = null; + let element: IHTMLElement = null; beforeEach(() => { window = new Window(); document = window.document; - element = document.createElement('div'); + element = document.createElement('div'); }); afterEach(() => { @@ -81,7 +83,7 @@ describe('HTMLElement', () => { }); describe('get innerText()', () => { - it('Returns the as the textContent property.', () => { + it('Returns the as the textContent property if element is not connected to document, but does not return script and style content.', () => { const div = document.createElement('div'); const script = document.createElement('script'); const style = document.createElement('script'); @@ -94,6 +96,16 @@ describe('HTMLElement', () => { style.appendChild(document.createTextNode('button { background: red; }')); expect(element.innerText).toBe('text1text2'); }); + + it('Returns the as the textContent property without any line breaks if element is not connected to document.', () => { + element.innerHTML = `
The quick brown fox
Jumped over the lazy dog
`; + expect(element.innerText).toBe('The quick brown foxJumped over the lazy dog'); + }); + + it('Returns the as the textContent property with line breaks between block elements if element is connected to document.', () => { + element.innerHTML = `
The quick brown fox
Jumped over the lazy dog
`; + expect(element.innerText).toBe('The quick brown fox\nJumped over the lazy dog'); + }); }); describe('set innerText()', () => { diff --git a/packages/happy-dom/test/window/Window.test.ts b/packages/happy-dom/test/window/Window.test.ts index 5c2239e25..0a04af406 100644 --- a/packages/happy-dom/test/window/Window.test.ts +++ b/packages/happy-dom/test/window/Window.test.ts @@ -1,4 +1,4 @@ -import CSSStyleDeclaration from '../../src/css/CSSStyleDeclaration'; +import CSSStyleDeclaration from '../../src/css/declaration/CSSStyleDeclaration'; import IDocument from '../../src/nodes/document/IDocument'; import IHTMLLinkElement from '../../src/nodes/html-link-element/IHTMLLinkElement'; import IHTMLElement from '../../src/nodes/html-element/IHTMLElement'; From d4e47b5edcc94ad498bd30df65b30855bad1fa73 Mon Sep 17 00:00:00 2001 From: Obada Khalili Date: Sun, 24 Jul 2022 08:52:49 +0300 Subject: [PATCH 08/84] #548@patch: Fix incorrect logic in selectEl.selectedIndex setter. --- package-lock.json | 84 +++++++++---------- .../html-select-element/HTMLSelectElement.ts | 2 +- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3c541db1e..a1630ecfe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2152,9 +2152,9 @@ } }, "node_modules/@lit/reactive-element": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.3.2.tgz", - "integrity": "sha512-A2e18XzPMrIh35nhIdE4uoqRzoIpEU5vZYuQN4S3Ee1zkGdYC27DP12pewbw/RLgPHzaE4kx/YqxMzebOpm0dA==" + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.3.4.tgz", + "integrity": "sha512-I1wz4uxOA52zSBhKmv4KQWLJpCyvfpnDg+eQR6mjpRgV+Ldi14HLPpSUpJklZRldz0fFmGCC/kVmuc/3cPFqCg==" }, "node_modules/@mrmlnc/readdir-enhanced": { "version": "2.2.1", @@ -2634,9 +2634,9 @@ "integrity": "sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==" }, "node_modules/@types/node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", + "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==", "dependencies": { "@types/node": "*", "form-data": "^3.0.0" @@ -4651,7 +4651,7 @@ "node_modules/cpy/node_modules/to-regex-range": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", "dependencies": { "is-number": "^3.0.0", "repeat-string": "^1.6.1" @@ -9041,9 +9041,9 @@ "dev": true }, "node_modules/lit": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/lit/-/lit-2.2.5.tgz", - "integrity": "sha512-Ln463c0xJZfzVxBcHddNvFQQ8Z22NK7KgNmrzwFF1iESHUud412RRExzepj18wpTbusgwoTnOYuoTpo9uyNBaQ==", + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/lit/-/lit-2.2.8.tgz", + "integrity": "sha512-QjeNbi/H9LVIHR+u0OqsL+hs62a16m02JlJHYN48HcBuXyiPYR8JvzsTp5dYYS81l+b9Emp3UaGo82EheV0pog==", "dependencies": { "@lit/reactive-element": "^1.3.0", "lit-element": "^3.2.0", @@ -9051,18 +9051,18 @@ } }, "node_modules/lit-element": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.2.0.tgz", - "integrity": "sha512-HbE7yt2SnUtg5DCrWt028oaU4D5F4k/1cntAFHTkzY8ZIa8N0Wmu92PxSxucsQSOXlODFrICkQ5x/tEshKi13g==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.2.2.tgz", + "integrity": "sha512-6ZgxBR9KNroqKb6+htkyBwD90XGRiqKDHVrW/Eh0EZ+l+iC+u+v+w3/BA5NGi4nizAVHGYvQBHUDuSmLjPp7NQ==", "dependencies": { "@lit/reactive-element": "^1.3.0", "lit-html": "^2.2.0" } }, "node_modules/lit-html": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.2.5.tgz", - "integrity": "sha512-e56Y9V+RNA+SGYsWP2DGb/wad5Ccd3xUZYjmcmbeZcnc0wP4zFQRXeXn7W3bbfBekmHDK2dOnuYNYkg0bQjh/w==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.2.7.tgz", + "integrity": "sha512-JhqiAwO1l03kRe68uBZ0i2x4ef2S5szY9vvP411nlrFZIpKK4/hwnhA/15bqbvxe1lV3ipBdhaOzHmyOk7QIRg==", "dependencies": { "@types/trusted-types": "^2.0.2" } @@ -12192,7 +12192,7 @@ "node_modules/snapdragon/node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "engines": { "node": ">=0.10.0" } @@ -12437,7 +12437,7 @@ "node_modules/static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", "dependencies": { "define-property": "^0.2.5", "object-copy": "^0.1.0" @@ -12977,7 +12977,7 @@ "node_modules/to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", "dependencies": { "kind-of": "^3.0.2" }, @@ -13340,7 +13340,7 @@ "node_modules/unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", "dependencies": { "has-value": "^0.3.1", "isobject": "^3.0.0" @@ -13403,7 +13403,7 @@ "node_modules/urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", "deprecated": "Please see https://github.com/lydell/urix#deprecated" }, "node_modules/use": { @@ -15664,9 +15664,9 @@ } }, "@lit/reactive-element": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.3.2.tgz", - "integrity": "sha512-A2e18XzPMrIh35nhIdE4uoqRzoIpEU5vZYuQN4S3Ee1zkGdYC27DP12pewbw/RLgPHzaE4kx/YqxMzebOpm0dA==" + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.3.4.tgz", + "integrity": "sha512-I1wz4uxOA52zSBhKmv4KQWLJpCyvfpnDg+eQR6mjpRgV+Ldi14HLPpSUpJklZRldz0fFmGCC/kVmuc/3cPFqCg==" }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", @@ -16104,9 +16104,9 @@ "integrity": "sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==" }, "@types/node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", + "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==", "requires": { "@types/node": "*", "form-data": "^3.0.0" @@ -17658,7 +17658,7 @@ "to-regex-range": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", "requires": { "is-number": "^3.0.0", "repeat-string": "^1.6.1" @@ -21077,9 +21077,9 @@ "dev": true }, "lit": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/lit/-/lit-2.2.5.tgz", - "integrity": "sha512-Ln463c0xJZfzVxBcHddNvFQQ8Z22NK7KgNmrzwFF1iESHUud412RRExzepj18wpTbusgwoTnOYuoTpo9uyNBaQ==", + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/lit/-/lit-2.2.8.tgz", + "integrity": "sha512-QjeNbi/H9LVIHR+u0OqsL+hs62a16m02JlJHYN48HcBuXyiPYR8JvzsTp5dYYS81l+b9Emp3UaGo82EheV0pog==", "requires": { "@lit/reactive-element": "^1.3.0", "lit-element": "^3.2.0", @@ -21087,18 +21087,18 @@ } }, "lit-element": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.2.0.tgz", - "integrity": "sha512-HbE7yt2SnUtg5DCrWt028oaU4D5F4k/1cntAFHTkzY8ZIa8N0Wmu92PxSxucsQSOXlODFrICkQ5x/tEshKi13g==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.2.2.tgz", + "integrity": "sha512-6ZgxBR9KNroqKb6+htkyBwD90XGRiqKDHVrW/Eh0EZ+l+iC+u+v+w3/BA5NGi4nizAVHGYvQBHUDuSmLjPp7NQ==", "requires": { "@lit/reactive-element": "^1.3.0", "lit-html": "^2.2.0" } }, "lit-html": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.2.5.tgz", - "integrity": "sha512-e56Y9V+RNA+SGYsWP2DGb/wad5Ccd3xUZYjmcmbeZcnc0wP4zFQRXeXn7W3bbfBekmHDK2dOnuYNYkg0bQjh/w==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.2.7.tgz", + "integrity": "sha512-JhqiAwO1l03kRe68uBZ0i2x4ef2S5szY9vvP411nlrFZIpKK4/hwnhA/15bqbvxe1lV3ipBdhaOzHmyOk7QIRg==", "requires": { "@types/trusted-types": "^2.0.2" } @@ -23464,7 +23464,7 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" } } }, @@ -23700,7 +23700,7 @@ "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", "requires": { "define-property": "^0.2.5", "object-copy": "^0.1.0" @@ -24118,7 +24118,7 @@ "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", "requires": { "kind-of": "^3.0.2" }, @@ -24382,7 +24382,7 @@ "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", "requires": { "has-value": "^0.3.1", "isobject": "^3.0.0" @@ -24433,7 +24433,7 @@ "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==" }, "use": { "version": "3.1.1", diff --git a/packages/happy-dom/src/nodes/html-select-element/HTMLSelectElement.ts b/packages/happy-dom/src/nodes/html-select-element/HTMLSelectElement.ts index 0952f900c..649ce1627 100644 --- a/packages/happy-dom/src/nodes/html-select-element/HTMLSelectElement.ts +++ b/packages/happy-dom/src/nodes/html-select-element/HTMLSelectElement.ts @@ -171,7 +171,7 @@ export default class HTMLSelectElement extends HTMLElement implements IHTMLSelec * @param value Value. */ public set selectedIndex(value: number) { - if (this.options.length - 1 > value || value < 0) { + if (value > this.options.length - 1 || value < 0) { throw new DOMException( 'Select elements selected index must be valid', DOMExceptionNameEnum.indexSizeError From 949b2b1b15d35997fecaa54576e67451b2992736 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Wed, 27 Jul 2022 13:16:38 +0200 Subject: [PATCH 09/84] #344@trivial: Continue on CSSStyleDeclaration. --- .../ComputedStylePropertyParser.ts | 163 --- .../AbstractCSSStyleDeclaration.ts | 74 +- .../css/declaration/CSSStyleDeclaration.ts | 275 +++++ .../declaration/CSSStyleDeclarationUtility.ts | 54 - .../ICSSStyleDeclarationProperty.ts | 5 + .../CSSStyleDeclarationDefaultValues.ts | 295 +++++ .../CSSStyleDeclarationNodeDefaultValues.ts | 1095 +++++++++++++++++ .../CSSStyleDeclarationPropertyParser.ts | 227 ++++ .../CSSStyleDeclarationPropertyValidator.ts | 24 + .../utilities/CSSStyleDeclarationUtility.ts | 153 +++ .../src/nodes/html-element/HTMLElement.ts | 28 +- .../declaration/CSSStyleDeclaration.test.ts | 44 +- 12 files changed, 2140 insertions(+), 297 deletions(-) delete mode 100644 packages/happy-dom/src/css/computed-style/ComputedStylePropertyParser.ts delete mode 100644 packages/happy-dom/src/css/declaration/CSSStyleDeclarationUtility.ts create mode 100644 packages/happy-dom/src/css/declaration/ICSSStyleDeclarationProperty.ts create mode 100644 packages/happy-dom/src/css/declaration/config/CSSStyleDeclarationDefaultValues.ts create mode 100644 packages/happy-dom/src/css/declaration/config/CSSStyleDeclarationNodeDefaultValues.ts create mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyParser.ts create mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValidator.ts create mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationUtility.ts diff --git a/packages/happy-dom/src/css/computed-style/ComputedStylePropertyParser.ts b/packages/happy-dom/src/css/computed-style/ComputedStylePropertyParser.ts deleted file mode 100644 index 0faab3ae1..000000000 --- a/packages/happy-dom/src/css/computed-style/ComputedStylePropertyParser.ts +++ /dev/null @@ -1,163 +0,0 @@ -/** - * Computed style property parser. - */ -export default class ComputedStylePropertyParser { - /** - * Parses a style property. - * - * @param name Name. - * @param value Value. - * @returns Property value. - */ - public static parseProperty(name: string, value: string): { [k: string]: string } { - switch (name) { - case 'border': - const borderParts = value.split(/ +/); - - if (borderParts.length < 2) { - return {}; - } - - const borderWidth = this.toPixels(borderParts[0]); - const borderColor = this.toPixels(borderParts[0]); - - return { - border: `${borderWidth} ${borderParts[1]} ${borderColor}`, - 'border-top': `${borderWidth} ${borderParts[1]} ${borderColor}`, - 'border-bottom': `${borderWidth} ${borderParts[1]} ${borderColor}`, - 'border-left': `${borderWidth} ${borderParts[1]} ${borderColor}`, - 'border-right': `${borderWidth} ${borderParts[1]} ${borderColor}`, - 'border-top-width': borderWidth, - 'border-right-width': borderWidth, - 'border-bottom-width': borderWidth, - 'border-left-width': borderWidth, - 'border-top-style': borderParts[1], - 'border-right-style': borderParts[1], - 'border-bottom-style': borderParts[1], - 'border-left-style': borderParts[1], - 'border-top-color': borderColor, - 'border-right-color': borderColor, - 'border-bottom-color': borderColor, - 'border-left-color': borderColor, - - // TODO: How to parse image from the border value? - 'border-image-source': 'none', - 'border-image-slice': '100%', - 'border-image-width': '1', - 'border-image-outset': '0', - 'border-image-repeat': 'stretch' - }; - case 'border-left': - case 'border-bottom': - case 'border-right': - case 'border-top': - const borderPostionedParts = value.split(/ +/); - - if (borderPostionedParts.length < 2) { - return {}; - } - - const borderName = name.split('-')[1]; - const borderPositionedWidth = this.toPixels(borderPostionedParts[0]); - const borderPositionedColor = borderPostionedParts[2] - ? this.toRGB(borderPostionedParts[2]) - : 'rgb(0, 0, 0)'; - - return { - [`border-${borderName}`]: `${borderPositionedWidth} ${borderPostionedParts[1]} ${borderPositionedColor}`, - [`border-${borderName}-width`]: borderPositionedWidth, - [`border-${borderName}-style`]: borderPostionedParts[1], - [`border-${borderName}-color`]: borderPositionedColor - }; - case 'border-width': - const borderWidthValue = this.toPixels(value); - return { - 'border-top-width': borderWidthValue, - 'border-right-width': borderWidthValue, - 'border-bottom-width': borderWidthValue, - 'border-left-width': borderWidthValue - }; - case 'border-style': - return { - 'border-top-style': value, - 'border-right-style': value, - 'border-bottom-style': value, - 'border-left-style': value - }; - case 'border-color': - const borderColorValue = this.toRGB(value); - return { - 'border-top-color': borderColorValue, - 'border-right-color': borderColorValue, - 'border-bottom-color': borderColorValue, - 'border-left-color': borderColorValue - }; - case 'border-radius': - const borderRadiusParts = value.split(/ +/); - const borderRadiusTopLeftValue = this.toPixels(borderRadiusParts[0]); - const borderRadiusTopRightValue = borderRadiusParts[1] - ? this.toPixels(borderRadiusParts[1]) - : ''; - const borderRadiusBottomRightValue = borderRadiusParts[2] - ? this.toPixels(borderRadiusParts[2]) - : ''; - const borderRadiusBottomLeftValue = borderRadiusParts[3] - ? this.toPixels(borderRadiusParts[3]) - : ''; - return { - 'border-radius': `${borderRadiusTopLeftValue}${ - borderRadiusTopRightValue ? ` ${borderRadiusTopRightValue}` : '' - }${borderRadiusBottomRightValue ? ` ${borderRadiusBottomRightValue}` : ''}${ - borderRadiusBottomLeftValue ? ` ${borderRadiusBottomLeftValue}` : '' - }`, - 'border-top-left-radius': borderRadiusTopLeftValue || borderRadiusTopLeftValue, - 'border-top-right-radius': borderRadiusTopRightValue || borderRadiusTopLeftValue, - 'border-bottom-right-radius': borderRadiusBottomRightValue || borderRadiusTopLeftValue, - 'border-bottom-left-radius': - borderRadiusBottomLeftValue || borderRadiusTopRightValue || borderRadiusTopLeftValue - }; - case 'padding': - case 'margin': - const paddingParts = value.split(/ +/); - const paddingTopValue = this.toPixels(paddingParts[0]); - const paddingRightValue = paddingParts[1] ? this.toPixels(paddingParts[1]) : ''; - const paddingBottomValue = paddingParts[2] ? this.toPixels(paddingParts[2]) : ''; - const paddingLeftValue = paddingParts[2] ? this.toPixels(paddingParts[2]) : ''; - return { - [name]: `${paddingTopValue}${paddingRightValue ? ` ${paddingRightValue}` : ''}${ - paddingBottomValue ? ` ${paddingBottomValue}` : '' - }${paddingLeftValue ? ` ${paddingLeftValue}` : ''}`, - [`${name}-top`]: paddingTopValue, - [`${name}-right`]: paddingRightValue || paddingParts[0], - [`${name}-bottom`]: paddingBottomValue || paddingParts[0], - [`${name}-left`]: paddingLeftValue || paddingParts[1] || paddingParts[0] - }; - } - - return { - [name]: value - }; - } - - /** - * Returns value in pixels. - * - * @param value Value. - * @returns Value in pixels. - */ - private static toPixels(value: string): string { - // TODO: Fix convertion to pixels - return value; - } - - /** - * Returns value in pixels. - * - * @param value Value. - * @returns Value in RGB. - */ - private static toRGB(value: string): string { - // TODO: Fix convertion to RGB - return value; - } -} diff --git a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts index fe0be1ef1..b6f081261 100644 --- a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts +++ b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts @@ -3,7 +3,9 @@ import Attr from '../../nodes/attr/Attr'; import CSSRule from '../CSSRule'; import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum'; import DOMException from '../../exception/DOMException'; -import CSSStyleDeclarationUtility from './CSSStyleDeclarationUtility'; +import CSSStyleDeclarationUtility from './utilities/CSSStyleDeclarationUtility'; +import CSSStyleDeclarationDefaultValues from './config/CSSStyleDeclarationDefaultValues'; +import ICSSStyleDeclarationProperty from './ICSSStyleDeclarationProperty'; /** * CSS Style Declaration. @@ -11,17 +13,19 @@ import CSSStyleDeclarationUtility from './CSSStyleDeclarationUtility'; export default abstract class AbstractCSSStyleDeclaration { // Other properties public readonly parentRule: CSSRule = null; - public _readonly = false; - protected _styles: { [k: string]: string } = {}; + protected _styles: { [k: string]: ICSSStyleDeclarationProperty } = {}; protected _ownerElement: IElement; + protected _computed: boolean; /** * Constructor. * * @param [ownerElement] Computed style element. + * @param [computed] Computed. */ - constructor(ownerElement: IElement = null) { + constructor(ownerElement: IElement = null, computed = false) { this._ownerElement = ownerElement; + this._computed = computed; } /** @@ -31,17 +35,7 @@ export default abstract class AbstractCSSStyleDeclaration { */ public get length(): number { if (this._ownerElement) { - if ( - this._ownerElement['_attributes']['style'] && - this._ownerElement['_attributes']['style'].value - ) { - return Object.keys( - CSSStyleDeclarationUtility.styleStringToObject( - this._ownerElement['_attributes']['style'].value - ) - ).length; - } - return 0; + return CSSStyleDeclarationUtility.getElementStyleProperties(this._ownerElement).length; } return Object.keys(this._styles).length; @@ -54,17 +48,13 @@ export default abstract class AbstractCSSStyleDeclaration { */ public get cssText(): string { if (this._ownerElement) { - if ( - this._ownerElement['_attributes']['style'] && - this._ownerElement['_attributes']['style'].value - ) { - return CSSStyleDeclarationUtility.styleObjectToString( - CSSStyleDeclarationUtility.styleStringToObject( - this._ownerElement['_attributes']['style'].value - ) - ); + if (this._computed) { + return ''; } - return ''; + + return CSSStyleDeclarationUtility.styleObjectToString( + CSSStyleDeclarationUtility.getElementStyle(this._ownerElement) + ); } return CSSStyleDeclarationUtility.styleObjectToString(this._styles); @@ -76,7 +66,7 @@ export default abstract class AbstractCSSStyleDeclaration { * @param cssText CSS text. */ public set cssText(cssText: string) { - if (this._readonly) { + if (this._computed) { throw new DOMException( `Failed to execute 'cssText' on 'CSSStyleDeclaration': These styles are computed, and the properties are therefore read-only.`, DOMExceptionNameEnum.domException @@ -111,19 +101,12 @@ export default abstract class AbstractCSSStyleDeclaration { */ public item(index: number): string { if (this._ownerElement) { - if ( - this._ownerElement['_attributes']['style'] && - this._ownerElement['_attributes']['style'].value - ) { - return ( - Object.keys( - CSSStyleDeclarationUtility.styleStringToObject( - this._ownerElement['_attributes']['style'].value - ) - )[index] || '' - ); + if (this._computed) { + return Object.keys(CSSStyleDeclarationDefaultValues)[index] || ''; } - return ''; + return ( + Object.keys(CSSStyleDeclarationUtility.getElementStyle(this._ownerElement))[index] || '' + ); } return Object.keys(this._styles)[index] || ''; } @@ -140,7 +123,7 @@ export default abstract class AbstractCSSStyleDeclaration { value: string, priority?: 'important' | '' | undefined ): void { - if (this._readonly) { + if (this._computed) { throw new DOMException( `Failed to execute 'setProperty' on 'CSSStyleDeclaration': These styles are computed, and therefore the '${propertyName}' property is read-only.`, DOMExceptionNameEnum.domException @@ -183,7 +166,7 @@ export default abstract class AbstractCSSStyleDeclaration { * @param [priority] Can be "important", or an empty string. */ public removeProperty(propertyName: string): void { - if (this._readonly) { + if (this._computed) { throw new DOMException( `Failed to execute 'removeProperty' on 'CSSStyleDeclaration': These styles are computed, and therefore the '${propertyName}' property is read-only.`, DOMExceptionNameEnum.domException @@ -219,15 +202,8 @@ export default abstract class AbstractCSSStyleDeclaration { */ public getPropertyValue(propertyName: string): string { if (this._ownerElement) { - if ( - !this._ownerElement['_attributes']['style'] || - !this._ownerElement['_attributes']['style'].value - ) { - return ''; - } - const value = CSSStyleDeclarationUtility.styleStringToObject( - this._ownerElement['_attributes']['style'].value - )[propertyName]; + const elementStyle = CSSStyleDeclarationUtility.getElementStyle(this._ownerElement); + const value = elementStyle[propertyName]; return value ? value.replace(' !important', '') : ''; } diff --git a/packages/happy-dom/src/css/declaration/CSSStyleDeclaration.ts b/packages/happy-dom/src/css/declaration/CSSStyleDeclaration.ts index 7c48e55e6..f39ac2004 100644 --- a/packages/happy-dom/src/css/declaration/CSSStyleDeclaration.ts +++ b/packages/happy-dom/src/css/declaration/CSSStyleDeclaration.ts @@ -1485,11 +1485,118 @@ export default class CSSStyleDeclaration extends AbstractCSSStyleDeclaration { public get 368(): string { return this.item(368) || undefined; } + public get 369(): string { + return this.item(369) || undefined; + } + + public get 370(): string { + return this.item(370) || undefined; + } + + public get 371(): string { + return this.item(371) || undefined; + } + + public get 372(): string { + return this.item(372) || undefined; + } + + public get 373(): string { + return this.item(373) || undefined; + } + + public get 374(): string { + return this.item(374) || undefined; + } + + public get 375(): string { + return this.item(375) || undefined; + } + + public get 376(): string { + return this.item(376) || undefined; + } + + public get 377(): string { + return this.item(377) || undefined; + } + + public get 378(): string { + return this.item(378) || undefined; + } + + public get 379(): string { + return this.item(379) || undefined; + } + + public get 380(): string { + return this.item(380) || undefined; + } + + public get 381(): string { + return this.item(381) || undefined; + } + + public get 382(): string { + return this.item(382) || undefined; + } + + public get 383(): string { + return this.item(383) || undefined; + } + + public get 384(): string { + return this.item(384) || undefined; + } + + public get 385(): string { + return this.item(385) || undefined; + } + + public get 386(): string { + return this.item(386) || undefined; + } + + public get 387(): string { + return this.item(387) || undefined; + } + + public get 388(): string { + return this.item(388) || undefined; + } + + public get 389(): string { + return this.item(389) || undefined; + } + + public get 390(): string { + return this.item(390) || undefined; + } + + public get 391(): string { + return this.item(391) || undefined; + } /** * CSS properties */ + public get accentColor(): string { + return this.getPropertyValue('accent-color'); + } + + public set accentColor(value: string) { + this.setProperty('accent-color', value); + } + + public get appRegion(): string { + return this.getPropertyValue('app-region'); + } + + public set appRegion(value: string) { + this.setProperty('app-region', value); + } + public get alignContent(): string { return this.getPropertyValue('align-content'); } @@ -2146,6 +2253,38 @@ export default class CSSStyleDeclaration extends AbstractCSSStyleDeclaration { this.setProperty('border-width', value); } + public get borderEndEndRadius(): string { + return this.getPropertyValue('border-end-end-radius'); + } + + public set borderEndEndRadius(value: string) { + this.setProperty('border-end-end-radius', value); + } + + public get borderEndStartRadius(): string { + return this.getPropertyValue('border-end-start-radius'); + } + + public set borderEndStartRadius(value: string) { + this.setProperty('border-end-start-radius', value); + } + + public get borderStartEndRadius(): string { + return this.getPropertyValue('border-start-end-radius'); + } + + public set borderStartEndRadius(value: string) { + this.setProperty('border-start-end-radius', value); + } + + public get borderStartStartRadius(): string { + return this.getPropertyValue('border-start-start-radius'); + } + + public set borderStartStartRadius(value: string) { + this.setProperty('border-start-start-radius', value); + } + public get bottom(): string { return this.getPropertyValue('bottom'); } @@ -2426,6 +2565,38 @@ export default class CSSStyleDeclaration extends AbstractCSSStyleDeclaration { this.setProperty('counter-set', value); } + public get containIntrinsicBlockSize(): string { + return this.getPropertyValue('contain-intrinsic-block-size'); + } + + public set containIntrinsicBlockSize(value: string) { + this.setProperty('contain-intrinsic-block-size', value); + } + + public get containIntrinsicHeight(): string { + return this.getPropertyValue('contain-intrinsic-height'); + } + + public set containIntrinsicHeight(value: string) { + this.setProperty('contain-intrinsic-height', value); + } + + public get containIntrinsicInlineSize(): string { + return this.getPropertyValue('contain-intrinsic-inline-size'); + } + + public set containIntrinsicInlineSize(value: string) { + this.setProperty('contain-intrinsic-inline-size', value); + } + + public get containIntrinsicWidth(): string { + return this.getPropertyValue('contain-intrinsic-width'); + } + + public set containIntrinsicWidth(value: string) { + this.setProperty('contain-intrinsic-width', value); + } + public get cssFloat(): string { return this.getPropertyValue('css-float'); } @@ -2730,6 +2901,38 @@ export default class CSSStyleDeclaration extends AbstractCSSStyleDeclaration { this.setProperty('font-variation-settings', value); } + public get fontPalette(): string { + return this.getPropertyValue('font-palette'); + } + + public set fontPalette(value: string) { + this.setProperty('font-palette', value); + } + + public get fontSynthesisSmallCaps(): string { + return this.getPropertyValue('font-synthesis-small-caps'); + } + + public set fontSynthesisSmallCaps(value: string) { + this.setProperty('font-synthesis-small-caps', value); + } + + public get fontSynthesisStyle(): string { + return this.getPropertyValue('font-synthesis-style'); + } + + public set fontSynthesisStyle(value: string) { + this.setProperty('font-synthesis-style', value); + } + + public get fontSynthesisWeight(): string { + return this.getPropertyValue('font-synthesis-weight'); + } + + public set fontSynthesisWeight(value: string) { + this.setProperty('font-synthesis-weight', value); + } + public get fontWeight(): string { return this.getPropertyValue('font-weight'); } @@ -2954,6 +3157,38 @@ export default class CSSStyleDeclaration extends AbstractCSSStyleDeclaration { this.setProperty('isolation', value); } + public get insetBlockEnd(): string { + return this.getPropertyValue('inset-block-end'); + } + + public set insetBlockEnd(value: string) { + this.setProperty('inset-block-end', value); + } + + public get insetBlockStart(): string { + return this.getPropertyValue('inset-block-start'); + } + + public set insetBlockStart(value: string) { + this.setProperty('inset-block-start', value); + } + + public get insetInlineEnd(): string { + return this.getPropertyValue('inset-inline-end'); + } + + public set insetInlineEnd(value: string) { + this.setProperty('inset-inline-end', value); + } + + public get insetInlineStart(): string { + return this.getPropertyValue('inset-inline-start'); + } + + public set insetInlineStart(value: string) { + this.setProperty('inset-inline-start', value); + } + public get justifyContent(): string { return this.getPropertyValue('justify-content'); } @@ -3458,6 +3693,14 @@ export default class CSSStyleDeclaration extends AbstractCSSStyleDeclaration { this.setProperty('overscroll-behavior-y', value); } + public get overflowClipMargin(): string { + return this.getPropertyValue('overflow-clip-margin'); + } + + public set overflowClipMargin(value: string) { + this.setProperty('overflow-clip-margin', value); + } + public get padding(): string { return this.getPropertyValue('padding'); } @@ -4050,6 +4293,14 @@ export default class CSSStyleDeclaration extends AbstractCSSStyleDeclaration { this.setProperty('syntax', value); } + public get scrollbarGutter(): string { + return this.getPropertyValue('scrollbar-gutter'); + } + + public set scrollbarGutter(value: string) { + this.setProperty('scrollbar-gutter', value); + } + public get tabSize(): string { return this.getPropertyValue('tab-size'); } @@ -4290,6 +4541,30 @@ export default class CSSStyleDeclaration extends AbstractCSSStyleDeclaration { this.setProperty('transition-timing-function', value); } + public get textEmphasisColor(): string { + return this.getPropertyValue('text-emphasis-color'); + } + + public set textEmphasisColor(value: string) { + this.setProperty('text-emphasis-color', value); + } + + public get textEmphasisPosition(): string { + return this.getPropertyValue('text-emphasis-position'); + } + + public set textEmphasisPosition(value: string) { + this.setProperty('text-emphasis-position', value); + } + + public get textEmphasisStyle(): string { + return this.getPropertyValue('text-emphasis-style'); + } + + public set textEmphasisStyle(value: string) { + this.setProperty('text-emphasis-style', value); + } + public get unicodeBidi(): string { return this.getPropertyValue('unicode-bidi'); } diff --git a/packages/happy-dom/src/css/declaration/CSSStyleDeclarationUtility.ts b/packages/happy-dom/src/css/declaration/CSSStyleDeclarationUtility.ts deleted file mode 100644 index c364d1098..000000000 --- a/packages/happy-dom/src/css/declaration/CSSStyleDeclarationUtility.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * CSS Style Declaration utility - */ -export default class CSSStyleDeclarationUtility { - /** - * Converts style string to object. - * - * @param styleString Style string (e.g. "border: 2px solid red; font-size: 12px;"). - * @param [cache] Cache. - * @returns Style object. - */ - public static styleStringToObject(styleString: string): { [k: string]: string } { - const styles = {}; - - if (styleString) { - const parts = styleString.split(';'); - - 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) { - styles[trimmedName] = trimmedValue; - } - } - } - } - } - - return styles; - } - - /** - * Converts style object to string. - * - * @param styleObject Style object. - * @returns Styles as string. - */ - public static styleObjectToString(styleObject: { [k: string]: string }): string { - const keys = Object.keys(styleObject); - let styleString = ''; - - for (const key of keys) { - if (styleString) { - styleString += ' '; - } - styleString += `${key}: ${styleObject[key]};`; - } - - return styleString; - } -} diff --git a/packages/happy-dom/src/css/declaration/ICSSStyleDeclarationProperty.ts b/packages/happy-dom/src/css/declaration/ICSSStyleDeclarationProperty.ts new file mode 100644 index 000000000..cd9da3719 --- /dev/null +++ b/packages/happy-dom/src/css/declaration/ICSSStyleDeclarationProperty.ts @@ -0,0 +1,5 @@ +export default interface ICSSStyleDeclarationProperty { + name: string; + value: string; + important: boolean; +} diff --git a/packages/happy-dom/src/css/declaration/config/CSSStyleDeclarationDefaultValues.ts b/packages/happy-dom/src/css/declaration/config/CSSStyleDeclarationDefaultValues.ts new file mode 100644 index 000000000..335e05b30 --- /dev/null +++ b/packages/happy-dom/src/css/declaration/config/CSSStyleDeclarationDefaultValues.ts @@ -0,0 +1,295 @@ +export default { + 'accent-color': 'auto', + 'align-content': 'normal', + 'align-items': 'normal', + 'align-self': 'auto', + 'alignment-baseline': 'auto', + 'animation-delay': '0s', + 'animation-direction': 'normal', + 'animation-duration': '0s', + 'animation-fill-mode': 'none', + 'animation-iteration-count': '1', + 'animation-name': 'none', + 'animation-play-state': 'running', + 'animation-timing-function': 'ease', + 'app-region': 'none', + appearance: 'none', + 'backdrop-filter': 'none', + 'backface-visibility': 'visible', + 'background-attachment': 'scroll', + 'background-blend-mode': 'normal', + 'background-clip': 'border-box', + 'background-color': 'rgba(0, 0, 0, 0)', + 'background-image': 'none', + 'background-origin': 'padding-box', + 'background-position': '0% 0%', + 'background-repeat': 'repeat', + 'background-size': 'auto', + 'baseline-shift': '0px', + 'block-size': 'auto', + 'border-block-end-color': 'rgb(0, 0, 0)', + 'border-block-end-style': 'none', + 'border-block-end-width': '0px', + 'border-block-start-color': 'rgb(0, 0, 0)', + 'border-block-start-style': 'none', + 'border-block-start-width': '0px', + 'border-bottom-color': 'rgb(0, 0, 0)', + 'border-bottom-left-radius': '0px', + 'border-bottom-right-radius': '0px', + 'border-bottom-style': 'none', + 'border-bottom-width': '0px', + 'border-collapse': 'separate', + 'border-end-end-radius': '0px', + 'border-end-start-radius': '0px', + 'border-image-outset': '0', + 'border-image-repeat': 'stretch', + 'border-image-slice': '100%', + 'border-image-source': 'none', + 'border-image-width': '1', + 'border-inline-end-color': 'rgb(0, 0, 0)', + 'border-inline-end-style': 'none', + 'border-inline-end-width': '0px', + 'border-inline-start-color': 'rgb(0, 0, 0)', + 'border-inline-start-style': 'none', + 'border-inline-start-width': '0px', + 'border-left-color': 'rgb(0, 0, 0)', + 'border-left-style': 'none', + 'border-left-width': '0px', + 'border-right-color': 'rgb(0, 0, 0)', + 'border-right-style': 'none', + 'border-right-width': '0px', + 'border-start-end-radius': '0px', + 'border-start-start-radius': '0px', + 'border-top-color': 'rgb(0, 0, 0)', + 'border-top-left-radius': '0px', + 'border-top-right-radius': '0px', + 'border-top-style': 'none', + 'border-top-width': '0px', + bottom: 'auto', + 'box-shadow': 'none', + 'box-sizing': 'content-box', + 'break-after': 'auto', + 'break-before': 'auto', + 'break-inside': 'auto', + 'buffered-rendering': 'auto', + 'caption-side': 'top', + 'caret-color': 'rgb(0, 0, 0)', + clear: 'none', + clip: 'auto', + 'clip-path': 'none', + 'clip-rule': 'nonzero', + color: 'rgb(0, 0, 0)', + 'color-interpolation': 'srgb', + 'color-interpolation-filters': 'linearrgb', + 'color-rendering': 'auto', + 'column-count': 'auto', + 'column-gap': 'normal', + 'column-rule-color': 'rgb(0, 0, 0)', + 'column-rule-style': 'none', + 'column-rule-width': '0px', + 'column-span': 'none', + 'column-width': 'auto', + 'contain-intrinsic-block-size': 'none', + 'contain-intrinsic-height': 'none', + 'contain-intrinsic-inline-size': 'none', + 'contain-intrinsic-size': 'none', + 'contain-intrinsic-width': 'none', + content: 'normal', + cursor: 'auto', + cx: '0px', + cy: '0px', + d: 'none', + direction: 'ltr', + display: 'inline', + 'dominant-baseline': 'auto', + 'empty-cells': 'show', + fill: 'rgb(0, 0, 0)', + 'fill-opacity': '1', + 'fill-rule': 'nonzero', + filter: 'none', + 'flex-basis': 'auto', + 'flex-direction': 'row', + 'flex-grow': '0', + 'flex-shrink': '1', + 'flex-wrap': 'nowrap', + float: 'none', + 'flood-color': 'rgb(0, 0, 0)', + 'flood-opacity': '1', + 'font-family': 'Roboto, system-ui, sans-serif', + 'font-kerning': 'auto', + 'font-optical-sizing': 'auto', + 'font-palette': 'normal', + 'font-size': '13px', + 'font-stretch': '100%', + 'font-style': 'normal', + 'font-synthesis-small-caps': 'auto', + 'font-synthesis-style': 'auto', + 'font-synthesis-weight': 'auto', + 'font-variant': 'normal', + 'font-variant-caps': 'normal', + 'font-variant-east-asian': 'normal', + 'font-variant-ligatures': 'normal', + 'font-variant-numeric': 'normal', + 'font-weight': '400', + 'grid-auto-columns': 'auto', + 'grid-auto-flow': 'row', + 'grid-auto-rows': 'auto', + 'grid-column-end': 'auto', + 'grid-column-start': 'auto', + 'grid-row-end': 'auto', + 'grid-row-start': 'auto', + 'grid-template-areas': 'none', + 'grid-template-columns': 'none', + 'grid-template-rows': 'none', + height: 'auto', + hyphens: 'manual', + 'image-orientation': 'from-image', + 'image-rendering': 'auto', + 'inline-size': 'auto', + 'inset-block-end': 'auto', + 'inset-block-start': 'auto', + 'inset-inline-end': 'auto', + 'inset-inline-start': 'auto', + isolation: 'auto', + 'justify-content': 'normal', + 'justify-items': 'normal', + 'justify-self': 'auto', + left: 'auto', + 'letter-spacing': 'normal', + 'lighting-color': 'rgb(255, 255, 255)', + 'line-break': 'auto', + 'line-height': 'normal', + 'list-style-image': 'none', + 'list-style-position': 'outside', + 'list-style-type': 'disc', + 'margin-block-end': '0px', + 'margin-block-start': '0px', + 'margin-bottom': '0px', + 'margin-inline-end': '0px', + 'margin-inline-start': '0px', + 'margin-left': '0px', + 'margin-right': '0px', + 'margin-top': '0px', + 'marker-end': 'none', + 'marker-mid': 'none', + 'marker-start': 'none', + 'mask-type': 'luminance', + 'max-block-size': 'none', + 'max-height': 'none', + 'max-inline-size': 'none', + 'max-width': 'none', + 'min-block-size': '0px', + 'min-height': '0px', + 'min-inline-size': '0px', + 'min-width': '0px', + 'mix-blend-mode': 'normal', + 'object-fit': 'fill', + 'object-position': '50% 50%', + 'offset-distance': '0px', + 'offset-path': 'none', + 'offset-rotate': 'auto 0deg', + opacity: '1', + order: '0', + orphans: '2', + 'outline-color': 'rgb(0, 0, 0)', + 'outline-offset': '0px', + 'outline-style': 'none', + 'outline-width': '0px', + 'overflow-anchor': 'auto', + 'overflow-clip-margin': '0px', + 'overflow-wrap': 'normal', + 'overflow-x': 'visible', + 'overflow-y': 'visible', + 'overscroll-behavior-block': 'auto', + 'overscroll-behavior-inline': 'auto', + 'padding-block-end': '0px', + 'padding-block-start': '0px', + 'padding-bottom': '0px', + 'padding-inline-end': '0px', + 'padding-inline-start': '0px', + 'padding-left': '0px', + 'padding-right': '0px', + 'padding-top': '0px', + 'paint-order': 'normal', + perspective: 'none', + 'perspective-origin': '0px 0px', + 'pointer-events': 'auto', + position: 'static', + r: '0px', + resize: 'none', + right: 'auto', + 'row-gap': 'normal', + 'ruby-position': 'over', + rx: 'auto', + ry: 'auto', + 'scroll-behavior': 'auto', + 'scroll-margin-block-end': '0px', + 'scroll-margin-block-start': '0px', + 'scroll-margin-inline-end': '0px', + 'scroll-margin-inline-start': '0px', + 'scroll-padding-block-end': 'auto', + 'scroll-padding-block-start': 'auto', + 'scroll-padding-inline-end': 'auto', + 'scroll-padding-inline-start': 'auto', + 'scrollbar-gutter': 'auto', + 'shape-image-threshold': '0', + 'shape-margin': '0px', + 'shape-outside': 'none', + 'shape-rendering': 'auto', + speak: 'normal', + 'stop-color': 'rgb(0, 0, 0)', + 'stop-opacity': '1', + stroke: 'none', + 'stroke-dasharray': 'none', + 'stroke-dashoffset': '0px', + 'stroke-linecap': 'butt', + 'stroke-linejoin': 'miter', + 'stroke-miterlimit': '4', + 'stroke-opacity': '1', + 'stroke-width': '1px', + 'tab-size': '8', + 'table-layout': 'auto', + 'text-align': 'start', + 'text-align-last': 'auto', + 'text-anchor': 'start', + 'text-decoration': 'none solid rgb(0, 0, 0)', + 'text-decoration-color': 'rgb(0, 0, 0)', + 'text-decoration-line': 'none', + 'text-decoration-skip-ink': 'auto', + 'text-decoration-style': 'solid', + 'text-emphasis-color': 'rgb(0, 0, 0)', + 'text-emphasis-position': 'over', + 'text-emphasis-style': 'none', + 'text-indent': '0px', + 'text-overflow': 'clip', + 'text-rendering': 'auto', + 'text-shadow': 'none', + 'text-size-adjust': 'auto', + 'text-transform': 'none', + 'text-underline-position': 'auto', + top: 'auto', + 'touch-action': 'auto', + transform: 'none', + 'transform-origin': '0px 0px', + 'transform-style': 'flat', + 'transition-delay': '0s', + 'transition-duration': '0s', + 'transition-property': 'all', + 'transition-timing-function': 'ease', + 'unicode-bidi': 'normal', + 'user-select': 'auto', + 'vector-effect': 'none', + 'vertical-align': 'baseline', + visibility: 'visible', + 'white-space': 'normal', + widows: '2', + width: 'auto', + 'will-change': 'auto', + 'word-break': 'normal', + 'word-spacing': '0px', + 'writing-mode': 'horizontal-tb', + x: '0px', + y: '0px', + 'z-index': 'auto', + zoom: '1' +}; diff --git a/packages/happy-dom/src/css/declaration/config/CSSStyleDeclarationNodeDefaultValues.ts b/packages/happy-dom/src/css/declaration/config/CSSStyleDeclarationNodeDefaultValues.ts new file mode 100644 index 000000000..63df45eb1 --- /dev/null +++ b/packages/happy-dom/src/css/declaration/config/CSSStyleDeclarationNodeDefaultValues.ts @@ -0,0 +1,1095 @@ +export default { + A: {}, + ABBR: {}, + ADDRESS: { + 'block-size': '0px', + display: 'block', + 'font-style': 'italic', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + AREA: {}, + ARTICLE: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + ASIDE: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + AUDIO: { + 'block-size': '54px', + display: 'none', + height: '54px', + 'inline-size': '300px', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%', + width: '300px' + }, + B: { + 'font-weight': '700' + }, + BASE: { + display: 'none', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + BDI: { + 'unicode-bidi': 'isolate' + }, + BDO: { + 'unicode-bidi': 'bidi-override' + }, + BLOCKQUAOTE: {}, + BODY: { + 'background-color': 'rgb(255, 255, 255)', + 'block-size': '0px', + display: 'block', + 'font-size': '10.5625px', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + TEMPLATE: { + display: 'none', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + FORM: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + INPUT: { + appearance: 'auto', + 'background-color': 'rgb(255, 255, 255)', + 'block-size': '15.5px', + 'border-block-end-color': 'rgb(118, 118, 118)', + 'border-block-end-style': 'inset', + 'border-block-end-width': '2px', + 'border-block-start-color': 'rgb(118, 118, 118)', + 'border-block-start-style': 'inset', + 'border-block-start-width': '2px', + 'border-bottom-color': 'rgb(118, 118, 118)', + 'border-bottom-style': 'inset', + 'border-bottom-width': '2px', + 'border-inline-end-color': 'rgb(118, 118, 118)', + 'border-inline-end-style': 'inset', + 'border-inline-end-width': '2px', + 'border-inline-start-color': 'rgb(118, 118, 118)', + 'border-inline-start-style': 'inset', + 'border-inline-start-width': '2px', + 'border-left-color': 'rgb(118, 118, 118)', + 'border-left-style': 'inset', + 'border-left-width': '2px', + 'border-right-color': 'rgb(118, 118, 118)', + 'border-right-style': 'inset', + 'border-right-width': '2px', + 'border-top-color': 'rgb(118, 118, 118)', + 'border-top-style': 'inset', + 'border-top-width': '2px', + cursor: 'text', + display: 'inline-block', + 'font-family': 'Arial', + 'font-size': '13.3333px', + height: '15.5px', + 'inline-size': '139px', + 'padding-block-end': '1px', + 'padding-block-start': '1px', + 'padding-bottom': '1px', + 'padding-inline-end': '2px', + 'padding-inline-start': '2px', + 'padding-left': '2px', + 'padding-right': '2px', + 'padding-top': '1px', + 'perspective-origin': '73.5px 10.75px', + 'transform-origin': '73.5px 10.75px', + width: '139px' + }, + TEXTAREA: { + appearance: 'auto', + 'background-color': 'rgb(255, 255, 255)', + 'block-size': '31px', + 'border-block-end-color': 'rgb(118, 118, 118)', + 'border-block-end-style': 'solid', + 'border-block-end-width': '1px', + 'border-block-start-color': 'rgb(118, 118, 118)', + 'border-block-start-style': 'solid', + 'border-block-start-width': '1px', + 'border-bottom-color': 'rgb(118, 118, 118)', + 'border-bottom-style': 'solid', + 'border-bottom-width': '1px', + 'border-inline-end-color': 'rgb(118, 118, 118)', + 'border-inline-end-style': 'solid', + 'border-inline-end-width': '1px', + 'border-inline-start-color': 'rgb(118, 118, 118)', + 'border-inline-start-style': 'solid', + 'border-inline-start-width': '1px', + 'border-left-color': 'rgb(118, 118, 118)', + 'border-left-style': 'solid', + 'border-left-width': '1px', + 'border-right-color': 'rgb(118, 118, 118)', + 'border-right-style': 'solid', + 'border-right-width': '1px', + 'border-top-color': 'rgb(118, 118, 118)', + 'border-top-style': 'solid', + 'border-top-width': '1px', + cursor: 'text', + display: 'inline-block', + 'font-family': 'monospace', + 'font-size': '13.3333px', + height: '31px', + 'inline-size': '176px', + 'overflow-wrap': 'break-word', + 'overflow-x': 'auto', + 'overflow-y': 'auto', + 'padding-block-end': '2px', + 'padding-block-start': '2px', + 'padding-bottom': '2px', + 'padding-inline-end': '2px', + 'padding-inline-start': '2px', + 'padding-left': '2px', + 'padding-right': '2px', + 'padding-top': '2px', + 'perspective-origin': '91px 18.5px', + resize: 'both', + 'transform-origin': '91px 18.5px', + 'white-space': 'pre-wrap', + width: '176px' + }, + SCRIPT: { + display: 'none', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + IMG: { + 'block-size': '0px', + height: '0px', + 'inline-size': '0px', + width: '0px' + }, + LINK: { + display: 'none', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + STYLE: { + display: 'none', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + LABEL: { + cursor: 'default' + }, + SLOT: { + display: 'contents', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + SVG: {}, + CIRCLE: {}, + ELLIPSE: {}, + LINE: {}, + PATH: {}, + POLYGON: {}, + POLYLINE: {}, + RECT: {}, + STOP: {}, + USE: {}, + META: { + display: 'none', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + BLOCKQUOTE: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1432px', + 'margin-block-end': '13px', + 'margin-block-start': '13px', + 'margin-bottom': '13px', + 'margin-inline-end': '40px', + 'margin-inline-start': '40px', + 'margin-left': '40px', + 'margin-right': '40px', + 'margin-top': '13px', + 'perspective-origin': '716px 0px', + 'transform-origin': '716px 0px', + width: '1432px' + }, + BR: {}, + BUTTON: { + 'align-items': 'flex-start', + appearance: 'auto', + 'background-color': 'rgb(239, 239, 239)', + 'block-size': '6px', + 'border-block-end-color': 'rgb(118, 118, 118)', + 'border-block-end-style': 'outset', + 'border-block-end-width': '2px', + 'border-block-start-color': 'rgb(118, 118, 118)', + 'border-block-start-style': 'outset', + 'border-block-start-width': '2px', + 'border-bottom-color': 'rgb(118, 118, 118)', + 'border-bottom-style': 'outset', + 'border-bottom-width': '2px', + 'border-inline-end-color': 'rgb(118, 118, 118)', + 'border-inline-end-style': 'outset', + 'border-inline-end-width': '2px', + 'border-inline-start-color': 'rgb(118, 118, 118)', + 'border-inline-start-style': 'outset', + 'border-inline-start-width': '2px', + 'border-left-color': 'rgb(118, 118, 118)', + 'border-left-style': 'outset', + 'border-left-width': '2px', + 'border-right-color': 'rgb(118, 118, 118)', + 'border-right-style': 'outset', + 'border-right-width': '2px', + 'border-top-color': 'rgb(118, 118, 118)', + 'border-top-style': 'outset', + 'border-top-width': '2px', + 'box-sizing': 'border-box', + cursor: 'default', + display: 'inline-block', + 'font-size': '13.3333px', + height: '6px', + 'inline-size': '16px', + 'padding-block-end': '1px', + 'padding-block-start': '1px', + 'padding-bottom': '1px', + 'padding-inline-end': '6px', + 'padding-inline-start': '6px', + 'padding-left': '6px', + 'padding-right': '6px', + 'padding-top': '1px', + 'perspective-origin': '8px 3px', + 'text-align': 'center', + 'transform-origin': '8px 3px', + width: '16px' + }, + CANVAS: { + 'block-size': '150px', + height: '150px', + 'inline-size': '300px', + 'perspective-origin': '150px 75px', + 'transform-origin': '150px 75px', + width: '300px' + }, + CAPTION: { + 'block-size': '0px', + display: 'table-caption', + height: '0px', + 'inline-size': '0px', + 'text-align': '-webkit-center', + width: '0px' + }, + CITE: { + 'font-style': 'italic' + }, + CODE: { + 'font-family': 'monospace', + 'font-size': '10.5625px' + }, + COL: { + 'block-size': '0px', + display: 'table-column', + height: '0px', + 'inline-size': '0px', + width: '0px' + }, + COLGROUP: { + 'block-size': '0px', + display: 'table-column-group', + height: '0px', + 'inline-size': '0px', + width: '0px' + }, + DATA: {}, + DATALIST: { + display: 'none', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + DD: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1472px', + 'margin-inline-start': '40px', + 'margin-left': '40px', + 'perspective-origin': '736px 0px', + 'transform-origin': '736px 0px', + width: '1472px' + }, + DEL: { + 'text-decoration': 'line-through solid rgb(0, 0, 0)', + 'text-decoration-line': 'line-through', + '-webkit-text-decorations-in-effect': 'line-through' + }, + DETAILS: { + 'block-size': '15px', + display: 'block', + height: '15px', + 'inline-size': '1000px', + 'perspective-origin': '756px 7.5px', + 'transform-origin': '756px 7.5px', + width: '1000px' + }, + DFN: { + 'font-style': 'italic' + }, + DIALOG: { + 'background-color': 'rgb(255, 255, 255)', + 'block-size': 'fit-content', + 'border-block-end-style': 'solid', + 'border-block-end-width': '1.5px', + 'border-block-start-style': 'solid', + 'border-block-start-width': '1.5px', + 'border-bottom-style': 'solid', + 'border-bottom-width': '1.5px', + 'border-inline-end-style': 'solid', + 'border-inline-end-width': '1.5px', + 'border-inline-start-style': 'solid', + 'border-inline-start-width': '1.5px', + 'border-left-style': 'solid', + 'border-left-width': '1.5px', + 'border-right-style': 'solid', + 'border-right-width': '1.5px', + 'border-top-style': 'solid', + 'border-top-width': '1.5px', + display: 'none', + height: 'fit-content', + 'inline-size': 'fit-content', + 'inset-inline-end': '0px', + 'inset-inline-start': '0px', + left: '0px', + 'margin-block-end': 'auto', + 'margin-block-start': 'auto', + 'margin-bottom': 'auto', + 'margin-inline-end': 'auto', + 'margin-inline-start': 'auto', + 'margin-left': 'auto', + 'margin-right': 'auto', + 'margin-top': 'auto', + 'padding-block-end': '13px', + 'padding-block-start': '13px', + 'padding-bottom': '13px', + 'padding-inline-end': '13px', + 'padding-inline-start': '13px', + 'padding-left': '13px', + 'padding-right': '13px', + 'padding-top': '13px', + 'perspective-origin': '50% 50%', + position: 'absolute', + right: '0px', + 'transform-origin': '50% 50%', + width: 'fit-content' + }, + DIV: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + DL: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'margin-block-end': '13px', + 'margin-block-start': '13px', + 'margin-bottom': '13px', + 'margin-top': '13px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + DT: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + EM: { + 'font-style': 'italic' + }, + EMBED: { + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + FIELDSET: { + 'block-size': '0px', + 'border-block-end-color': 'rgb(192, 192, 192)', + 'border-block-end-style': 'groove', + 'border-block-end-width': '2px', + 'border-block-start-color': 'rgb(192, 192, 192)', + 'border-block-start-style': 'groove', + 'border-block-start-width': '2px', + 'border-bottom-color': 'rgb(192, 192, 192)', + 'border-bottom-style': 'groove', + 'border-bottom-width': '2px', + 'border-inline-end-color': 'rgb(192, 192, 192)', + 'border-inline-end-style': 'groove', + 'border-inline-end-width': '2px', + 'border-inline-start-color': 'rgb(192, 192, 192)', + 'border-inline-start-style': 'groove', + 'border-inline-start-width': '2px', + 'border-left-color': 'rgb(192, 192, 192)', + 'border-left-style': 'groove', + 'border-left-width': '2px', + 'border-right-color': 'rgb(192, 192, 192)', + 'border-right-style': 'groove', + 'border-right-width': '2px', + 'border-top-color': 'rgb(192, 192, 192)', + 'border-top-style': 'groove', + 'border-top-width': '2px', + display: 'block', + height: '0px', + 'inline-size': '1484.5px', + 'margin-inline-end': '2px', + 'margin-inline-start': '2px', + 'margin-left': '2px', + 'margin-right': '2px', + 'min-inline-size': 'min-content', + 'min-width': 'min-content', + 'padding-block-end': '8.125px', + 'padding-block-start': '4.55px', + 'padding-bottom': '8.125px', + 'padding-inline-end': '9.75px', + 'padding-inline-start': '9.75px', + 'padding-left': '9.75px', + 'padding-right': '9.75px', + 'padding-top': '4.55px', + 'perspective-origin': '754px 8.33594px', + 'transform-origin': '754px 8.33594px', + width: '1484.5px' + }, + FIGCAPTION: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + FIGURE: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1432px', + 'margin-block-end': '13px', + 'margin-block-start': '13px', + 'margin-bottom': '13px', + 'margin-inline-end': '40px', + 'margin-inline-start': '40px', + 'margin-left': '40px', + 'margin-right': '40px', + 'margin-top': '13px', + 'perspective-origin': '716px 0px', + 'transform-origin': '716px 0px', + width: '1432px' + }, + FOOTER: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + H1: { + 'block-size': '0px', + display: 'block', + 'font-size': '26px', + 'font-weight': '700', + height: '0px', + 'inline-size': '1000px', + 'margin-block-end': '17.42px', + 'margin-block-start': '17.42px', + 'margin-bottom': '17.42px', + 'margin-top': '17.42px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + H2: { + 'block-size': '0px', + display: 'block', + 'font-size': '19.5px', + 'font-weight': '700', + height: '0px', + 'inline-size': '1000px', + 'margin-block-end': '16.185px', + 'margin-block-start': '16.185px', + 'margin-bottom': '16.185px', + 'margin-top': '16.185px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + H3: { + 'block-size': '0px', + display: 'block', + 'font-size': '15.21px', + 'font-weight': '700', + height: '0px', + 'inline-size': '1000px', + 'margin-block-end': '15.21px', + 'margin-block-start': '15.21px', + 'margin-bottom': '15.21px', + 'margin-top': '15.21px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + H4: { + 'block-size': '0px', + display: 'block', + 'font-weight': '700', + height: '0px', + 'inline-size': '1000px', + 'margin-block-end': '17.29px', + 'margin-block-start': '17.29px', + 'margin-bottom': '17.29px', + 'margin-top': '17.29px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + H5: { + 'block-size': '0px', + display: 'block', + 'font-size': '10.79px', + 'font-weight': '700', + height: '0px', + 'inline-size': '1000px', + 'margin-block-end': '18.0193px', + 'margin-block-start': '18.0193px', + 'margin-bottom': '18.0193px', + 'margin-top': '18.0193px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + H6: { + 'block-size': '0px', + display: 'block', + 'font-size': '8.71px', + 'font-weight': '700', + height: '0px', + 'inline-size': '1000px', + 'margin-block-end': '20.2943px', + 'margin-block-start': '20.2943px', + 'margin-bottom': '20.2943px', + 'margin-top': '20.2943px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + HEAD: { + display: 'none', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + HEADER: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + HGROUP: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + HR: { + 'block-size': '0px', + 'border-block-end-style': 'inset', + 'border-block-end-width': '1px', + 'border-block-start-style': 'inset', + 'border-block-start-width': '1px', + 'border-bottom-style': 'inset', + 'border-bottom-width': '1px', + 'border-inline-end-style': 'inset', + 'border-inline-end-width': '1px', + 'border-inline-start-style': 'inset', + 'border-inline-start-width': '1px', + 'border-left-style': 'inset', + 'border-left-width': '1px', + 'border-right-style': 'inset', + 'border-right-width': '1px', + 'border-top-style': 'inset', + 'border-top-width': '1px', + display: 'block', + height: '0px', + 'inline-size': '1510px', + 'margin-block-end': '6.5px', + 'margin-block-start': '6.5px', + 'margin-bottom': '6.5px', + 'margin-top': '6.5px', + 'overflow-x': 'hidden', + 'overflow-y': 'hidden', + 'perspective-origin': '756px 1px', + 'transform-origin': '756px 1px', + 'unicode-bidi': 'isolate', + width: '1510px' + }, + HTML: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + I: { + 'font-style': 'italic' + }, + IFRAME: { + 'block-size': '150px', + 'border-block-end-style': 'inset', + 'border-block-end-width': '2px', + 'border-block-start-style': 'inset', + 'border-block-start-width': '2px', + 'border-bottom-style': 'inset', + 'border-bottom-width': '2px', + 'border-inline-end-style': 'inset', + 'border-inline-end-width': '2px', + 'border-inline-start-style': 'inset', + 'border-inline-start-width': '2px', + 'border-left-style': 'inset', + 'border-left-width': '2px', + 'border-right-style': 'inset', + 'border-right-width': '2px', + 'border-top-style': 'inset', + 'border-top-width': '2px', + height: '150px', + 'inline-size': '300px', + 'perspective-origin': '152px 77px', + 'transform-origin': '152px 77px', + width: '300px' + }, + INS: { + 'text-decoration': 'underline solid rgb(0, 0, 0)', + 'text-decoration-line': 'underline', + '-webkit-text-decorations-in-effect': 'underline' + }, + KBD: { + 'font-family': 'monospace', + 'font-size': '10.5625px' + }, + LEGEND: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1508px', + 'padding-inline-end': '2px', + 'padding-inline-start': '2px', + 'padding-left': '2px', + 'padding-right': '2px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1508px' + }, + LI: { + 'block-size': '15px', + display: 'list-item', + height: '15px', + 'inline-size': '1000px', + 'perspective-origin': '756px 7.5px', + 'text-align': 'left', + 'transform-origin': '756px 7.5px', + width: '1000px' + }, + MAIN: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + MAP: {}, + MARK: { + 'background-color': 'rgb(255, 255, 0)' + }, + MATH: {}, + MENU: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1472px', + 'margin-block-end': '13px', + 'margin-block-start': '13px', + 'margin-bottom': '13px', + 'margin-top': '13px', + 'padding-inline-start': '40px', + 'padding-left': '40px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1472px' + }, + MENUITEM: {}, + METER: { + appearance: 'auto', + 'block-size': '13px', + 'box-sizing': 'border-box', + display: 'inline-block', + height: '13px', + 'inline-size': '65px', + 'perspective-origin': '32.5px 6.5px', + 'transform-origin': '32.5px 6.5px', + 'vertical-align': '-2.6px', + width: '65px' + }, + NAV: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + NOSCRIPT: { + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + OBJECT: { + 'block-size': '150px', + height: '150px', + 'inline-size': '300px', + 'perspective-origin': '150px 75px', + 'transform-origin': '150px 75px', + width: '300px' + }, + OL: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1472px', + 'list-style-type': 'decimal', + 'margin-block-end': '13px', + 'margin-block-start': '13px', + 'margin-bottom': '13px', + 'margin-top': '13px', + 'padding-inline-start': '40px', + 'padding-left': '40px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1472px' + }, + OPTGROUP: { + 'block-size': '16.5938px', + display: 'block', + 'font-weight': '700', + height: '16.5938px', + 'inline-size': '1000px', + 'perspective-origin': '756px 8.29688px', + 'transform-origin': '756px 8.29688px', + width: '1000px' + }, + OPTION: { + 'block-size': '15.5938px', + display: 'block', + height: '15.5938px', + 'inline-size': '1508px', + 'min-block-size': '15.6px', + 'min-height': '15.6px', + 'padding-block-end': '1px', + 'padding-bottom': '1px', + 'padding-inline-end': '2px', + 'padding-inline-start': '2px', + 'padding-left': '2px', + 'padding-right': '2px', + 'perspective-origin': '756px 8.29688px', + 'transform-origin': '756px 8.29688px', + 'white-space': 'nowrap', + width: '1508px' + }, + OUTPUT: { + 'unicode-bidi': 'isolate' + }, + P: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'margin-block-end': '13px', + 'margin-block-start': '13px', + 'margin-bottom': '13px', + 'margin-top': '13px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + PARAM: { + display: 'none', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + PICTURE: {}, + PRE: { + 'block-size': '0px', + display: 'block', + 'font-family': 'monospace', + 'font-size': '10.5625px', + height: '0px', + 'inline-size': '1000px', + 'margin-block-end': '10.5625px', + 'margin-block-start': '10.5625px', + 'margin-bottom': '10.5625px', + 'margin-top': '10.5625px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + 'white-space': 'pre', + width: '1000px' + }, + PROGRESS: { + appearance: 'auto', + 'block-size': '13px', + 'box-sizing': 'border-box', + display: 'inline-block', + height: '13px', + 'inline-size': '130px', + 'perspective-origin': '65px 6.5px', + 'transform-origin': '65px 6.5px', + 'vertical-align': '-2.6px', + width: '130px' + }, + Q: {}, + RB: {}, + RP: { + display: 'none', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + RT: {}, + RTC: {}, + RUBY: {}, + S: { + 'text-decoration': 'line-through solid rgb(0, 0, 0)', + 'text-decoration-line': 'line-through', + '-webkit-text-decorations-in-effect': 'line-through' + }, + SAMP: { + 'font-family': 'monospace', + 'font-size': '10.5625px' + }, + SECTION: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + SELECT: { + 'align-items': 'center', + appearance: 'auto', + 'background-color': 'rgb(255, 255, 255)', + 'block-size': '19px', + 'border-block-end-color': 'rgb(118, 118, 118)', + 'border-block-end-style': 'solid', + 'border-block-end-width': '1px', + 'border-block-start-color': 'rgb(118, 118, 118)', + 'border-block-start-style': 'solid', + 'border-block-start-width': '1px', + 'border-bottom-color': 'rgb(118, 118, 118)', + 'border-bottom-style': 'solid', + 'border-bottom-width': '1px', + 'border-inline-end-color': 'rgb(118, 118, 118)', + 'border-inline-end-style': 'solid', + 'border-inline-end-width': '1px', + 'border-inline-start-color': 'rgb(118, 118, 118)', + 'border-inline-start-style': 'solid', + 'border-inline-start-width': '1px', + 'border-left-color': 'rgb(118, 118, 118)', + 'border-left-style': 'solid', + 'border-left-width': '1px', + 'border-right-color': 'rgb(118, 118, 118)', + 'border-right-style': 'solid', + 'border-right-width': '1px', + 'border-top-color': 'rgb(118, 118, 118)', + 'border-top-style': 'solid', + 'border-top-width': '1px', + 'box-sizing': 'border-box', + cursor: 'default', + display: 'inline-block', + 'font-family': 'Arial', + 'font-size': '13.3333px', + height: '19px', + 'inline-size': '22px', + 'perspective-origin': '11px 9.5px', + 'transform-origin': '11px 9.5px', + 'white-space': 'pre', + width: '22px' + }, + SMALL: { + 'font-size': '10.8333px' + }, + SOURCE: {}, + SPAN: {}, + STRONG: { + 'font-weight': '700' + }, + SUB: { + 'font-size': '10.8333px', + 'vertical-align': 'sub' + }, + SUMMARY: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1000px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1000px' + }, + SUP: { + 'font-size': '10.8333px', + 'vertical-align': 'super' + }, + TABLE: { + 'block-size': '0px', + 'border-block-end-color': 'rgb(128, 128, 128)', + 'border-block-start-color': 'rgb(128, 128, 128)', + 'border-bottom-color': 'rgb(128, 128, 128)', + 'border-inline-end-color': 'rgb(128, 128, 128)', + 'border-inline-start-color': 'rgb(128, 128, 128)', + 'border-left-color': 'rgb(128, 128, 128)', + 'border-right-color': 'rgb(128, 128, 128)', + 'border-top-color': 'rgb(128, 128, 128)', + 'box-sizing': 'border-box', + display: 'table', + height: '0px', + 'inline-size': '0px', + width: '0px', + '-webkit-border-horizontal-spacing': '2px', + '-webkit-border-vertical-spacing': '2px' + }, + TBODY: { + 'block-size': '0px', + display: 'table-row-group', + height: '0px', + 'inline-size': '0px', + 'vertical-align': 'middle', + width: '0px' + }, + TD: { + 'block-size': '0px', + display: 'table-cell', + height: '0px', + 'inline-size': '0px', + width: '0px' + }, + TFOOT: { + 'block-size': '0px', + display: 'table-footer-group', + height: '0px', + 'inline-size': '0px', + 'vertical-align': 'middle', + width: '0px' + }, + TH: { + 'block-size': '0px', + display: 'table-cell', + 'font-weight': '700', + height: '0px', + 'inline-size': '0px', + 'text-align': 'center', + width: '0px' + }, + THEAD: { + 'block-size': '0px', + display: 'table-header-group', + height: '0px', + 'inline-size': '0px', + 'vertical-align': 'middle', + width: '0px' + }, + TIME: {}, + TITLE: { + display: 'none', + 'perspective-origin': '50% 50%', + 'transform-origin': '50% 50%' + }, + TR: { + 'block-size': '0px', + display: 'table-row', + height: '0px', + 'inline-size': '0px', + width: '0px' + }, + TRACK: {}, + U: { + 'text-decoration': 'underline solid rgb(0, 0, 0)', + 'text-decoration-line': 'underline', + '-webkit-text-decorations-in-effect': 'underline' + }, + UL: { + 'block-size': '0px', + display: 'block', + height: '0px', + 'inline-size': '1472px', + 'margin-block-end': '13px', + 'margin-block-start': '13px', + 'margin-bottom': '13px', + 'margin-top': '13px', + 'padding-inline-start': '40px', + 'padding-left': '40px', + 'perspective-origin': '756px 0px', + 'transform-origin': '756px 0px', + width: '1472px' + }, + VAR: { + 'font-style': 'italic' + }, + VIDEO: { + 'block-size': '150px', + height: '150px', + 'inline-size': '300px', + 'object-fit': 'contain', + 'perspective-origin': '150px 75px', + 'transform-origin': '150px 75px', + width: '300px' + }, + WBR: {} +}; diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyParser.ts new file mode 100644 index 000000000..261595ae3 --- /dev/null +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyParser.ts @@ -0,0 +1,227 @@ +import ICSSStyleDeclarationProperty from '../ICSSStyleDeclarationProperty'; +import CSSStyleDeclarationPropertyValidator from './CSSStyleDeclarationPropertyValidator'; + +/** + * Computed style property parser. + */ +export default class CSSStyleDeclarationPropertyParser { + /** + * Returns style. + * + * @param style Style. + * @param propertyName Property name. + * @returns Element style properties. + */ + public static getPropertyValue( + style: { + [k: string]: ICSSStyleDeclarationProperty; + }, + propertyName: string + ): string { + switch (propertyName) { + case 'margin': + case 'padding': + const padding = style[`${propertyName}-top`]?.value; + if (!padding) { + return ''; + } + for (const property of ['right', 'bottom', 'left']) { + if (style[`${propertyName}-${property}`]?.value !== padding) { + return ''; + } + } + return padding; + case 'border': + case 'border-left': + case 'border-right': + case 'border-top': + case 'border-bottom': + const border = CSSStyleDeclarationBorderUtility.getBorder(styleProperties); + + switch (propertyName) { + case 'border': + return border.left.width && + border.left.width === border.right.width && + border.left.width === border.top.width && + border.left.width === border.bottom.width && + border.left.style && + border.left.style === border.right.style && + border.left.style === border.top.style && + border.left.style === border.bottom.style && + border.left.color === border.right.color && + border.left.color === border.top.color && + border.left.color === border.bottom.color + ? `${border.left.width} ${border.left.style} ${border.left.color}` + : ''; + case 'border-left': + return border.left.width && border.left.style && border.left.color + ? `${border.left.width} ${border.left.style} ${border.left.color}` + : ''; + case 'border-right': + return border.right.width && border.right.style && border.right.color + ? `${border.right.width} ${border.right.style} ${border.right.color}` + : ''; + case 'border-top': + return border.top.width && border.top.style && border.top.color + ? `${border.top.width} ${border.top.style} ${border.top.color}` + : ''; + case 'border-bottom': + return border.bottom.width && border.bottom.style && border.bottom.color + ? `${border.bottom.width} ${border.bottom.style} ${border.bottom.color}` + : ''; + } + } + + return ''; + } + + /** + * Returns valid properties. + * + * @param propertyName Name. + * @param propertyValue Value. + * @returns Properties. + */ + public static getValidProperties( + propertyName: string, + propertyValue: string + ): { [k: string]: ICSSStyleDeclarationProperty } { + const important = propertyValue.endsWith(' !important'); + const name = propertyName; + const value = propertyValue.replace(' !important', ''); + let parts; + + switch (propertyName) { + case 'border': + parts = value.split(/ +/); + + if ( + parts.length < 2 || + !CSSStyleDeclarationPropertyValidator.validateSize(parts[0]) || + !CSSStyleDeclarationPropertyValidator.validateColor(parts[2]) + ) { + return {}; + } + + return { + 'border-top-width': { name, important, value: parts[0] }, + 'border-right-width': { name, important, value: parts[0] }, + 'border-bottom-width': { name, important, value: parts[0] }, + 'border-left-width': { name, important, value: parts[0] }, + 'border-top-style': { name, important, value: parts[1] }, + 'border-right-style': { name, important, value: parts[1] }, + 'border-bottom-style': { name, important, value: parts[1] }, + 'border-left-style': { name, important, value: parts[1] }, + 'border-top-color': { name, important, value: parts[2] }, + 'border-right-color': { name, important, value: parts[2] }, + 'border-bottom-color': { name, important, value: parts[2] }, + 'border-left-color': { name, important, value: parts[2] } + }; + case 'border-left': + case 'border-bottom': + case 'border-right': + case 'border-top': + parts = value.split(/ +/); + + if ( + parts.length < 2 || + !CSSStyleDeclarationPropertyValidator.validateSize(parts[0]) || + !CSSStyleDeclarationPropertyValidator.validateColor(parts[2]) + ) { + return {}; + } + + const borderName = name.split('-')[1]; + + return { + [`border-${borderName}-width`]: { name, important, value: parts[0] }, + [`border-${borderName}-style`]: { name, important, value: parts[1] }, + [`border-${borderName}-color`]: { name, important, value: parts[2] } + }; + case 'border-width': + if (!CSSStyleDeclarationPropertyValidator.validateSize(value)) { + return {}; + } + return { + 'border-top-width': { name, important, value }, + 'border-right-width': { name, important, value }, + 'border-bottom-width': { name, important, value }, + 'border-left-width': { name, important, value } + }; + case 'border-style': + return { + 'border-top-style': { name, important, value }, + 'border-right-style': { name, important, value }, + 'border-bottom-style': { name, important, value }, + 'border-left-style': { name, important, value } + }; + case 'border-color': + if (!CSSStyleDeclarationPropertyValidator.validateColor(value)) { + return {}; + } + return { + 'border-top-color': { name, important, value }, + 'border-right-color': { name, important, value }, + 'border-bottom-color': { name, important, value }, + 'border-left-color': { name, important, value } + }; + case 'border-radius': + parts = value.split(/ +/); + if ( + !value || + !CSSStyleDeclarationPropertyValidator.validateSize(parts[0]) || + (parts[1] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[1])) || + (parts[2] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[2])) || + (parts[3] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[3])) + ) { + return {}; + } + return { + 'border-top-left-radius': { name, important, value: parts[0] || parts[0] }, + 'border-top-right-radius': { name, important, value: parts[1] || parts[0] }, + 'border-bottom-right-radius': { name, important, value: parts[2] || parts[0] }, + 'border-bottom-left-radius': { name, important, value: parts[3] || parts[1] || parts[0] } + }; + case 'padding': + case 'margin': + parts = value.split(/ +/); + if ( + !value || + !CSSStyleDeclarationPropertyValidator.validateSize(parts[0]) || + (parts[1] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[1])) || + (parts[2] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[2])) || + (parts[3] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[3])) + ) { + return {}; + } + return { + [`${name}-top`]: { name, important, value: parts[0] }, + [`${name}-right`]: { name, important, value: parts[1] || parts[0] }, + [`${name}-bottom`]: { name, important, value: parts[2] || parts[0] }, + [`${name}-left`]: { name, important, value: parts[3] || parts[1] || parts[0] } + }; + case 'padding-top': + case 'padding-bottom': + case 'padding-left': + case 'padding-right': + case 'margin-top': + case 'margin-bottom': + case 'margin-left': + case 'margin-right': + case 'border-top-width': + case 'border-bottom-width': + case 'border-left-width': + case 'border-right-width': + if (!CSSStyleDeclarationPropertyValidator.validateSize(value)) { + return {}; + } + return { + [name]: { name, important, value } + }; + } + + return { + [name]: { name, important, value } + }; + } +} diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValidator.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValidator.ts new file mode 100644 index 000000000..19250b27d --- /dev/null +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValidator.ts @@ -0,0 +1,24 @@ +/** + * Computed style property parser. + */ +export default class CSSStyleDeclarationPropertyValidator { + /** + * Validates size. + * + * @param _value Value. + * @returns "true" if valid. + */ + public static validateSize(_value: string): boolean { + return true; + } + + /** + * Validates color. + * + * @param _value Value. + * @returns "true" if valid. + */ + public static validateColor(_value: string): boolean { + return true; + } +} diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationUtility.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationUtility.ts new file mode 100644 index 000000000..55108136e --- /dev/null +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationUtility.ts @@ -0,0 +1,153 @@ +import IElement from '../../../nodes/element/IElement'; +import ICSSStyleDeclarationProperty from '../ICSSStyleDeclarationProperty'; +import CSSStyleDeclarationPropertyParser from './CSSStyleDeclarationPropertyParser'; + +/** + * CSS Style Declaration utility + */ +export default class CSSStyleDeclarationUtility { + /** + * Returns a style from a string. + * + * @param styleString Style string (e.g. "border: 2px solid red; font-size: 12px;"). + * @returns Style. + */ + public static stringToStyle(styleString: string): { + [k: string]: ICSSStyleDeclarationProperty; + } { + const style = {}; + const parts = styleString.split(';'); + + 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) { + Object.assign( + style, + CSSStyleDeclarationPropertyParser.parseProperty(trimmedName, trimmedValue) + ); + } + } + } + } + + return style; + } + + /** + * Returns a style string. + * + * @param style Style. + * @returns Styles as string. + */ + public static styleToString(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; + } + + /** + * Returns element style properties. + * + * @param element Element. + * @param [computed] Computed. + * @returns Element style properties. + */ + public static getElementStyle( + element: IElement, + computed = false + ): { + [k: string]: ICSSStyleDeclarationProperty; + } { + if (computed) { + // TODO: Add logic for style sheets + } + if (element['_attributes']['style'] && element['_attributes']['style'].value) { + return this.stringToStyle(element['_attributes']['style'].value); + } + + return {}; + } + + /** + * Returns style. + * + * @param style Style. + * @param propertyName Property name. + * @returns Element style properties. + */ + public static getPropertyValue( + style: { + [k: string]: ICSSStyleDeclarationProperty; + }, + propertyName: string + ): string { + switch (propertyName) { + case 'margin': + case 'padding': + const padding = style[`${propertyName}-top`]?.value; + if (!padding) { + return ''; + } + for (const property of ['right', 'bottom', 'left']) { + if (style[`${propertyName}-${property}`]?.value !== padding) { + return ''; + } + } + return padding; + case 'border': + case 'border-left': + case 'border-right': + case 'border-top': + case 'border-bottom': + const border = CSSStyleDeclarationBorderUtility.getBorder(styleProperties); + + switch (propertyName) { + case 'border': + return border.left.width && + border.left.width === border.right.width && + border.left.width === border.top.width && + border.left.width === border.bottom.width && + border.left.style && + border.left.style === border.right.style && + border.left.style === border.top.style && + border.left.style === border.bottom.style && + border.left.color === border.right.color && + border.left.color === border.top.color && + border.left.color === border.bottom.color + ? `${border.left.width} ${border.left.style} ${border.left.color}` + : ''; + case 'border-left': + return border.left.width && border.left.style && border.left.color + ? `${border.left.width} ${border.left.style} ${border.left.color}` + : ''; + case 'border-right': + return border.right.width && border.right.style && border.right.color + ? `${border.right.width} ${border.right.style} ${border.right.color}` + : ''; + case 'border-top': + return border.top.width && border.top.style && border.top.color + ? `${border.top.width} ${border.top.style} ${border.top.color}` + : ''; + case 'border-bottom': + return border.bottom.width && border.bottom.style && border.bottom.color + ? `${border.bottom.width} ${border.bottom.style} ${border.bottom.color}` + : ''; + } + } + + return ''; + } +} diff --git a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts index d71a21b0f..e6632bf1c 100644 --- a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts +++ b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts @@ -66,24 +66,24 @@ export default class HTMLElement extends Element implements IHTMLElement { let result = ''; for (const childNode of this.childNodes) { - if (childNode instanceof HTMLElement) { - if (childNode.tagName !== 'SCRIPT' && childNode.tagName !== 'STYLE') { - result += childNode.innerText; + if (childNode.nodeType === NodeTypeEnum.elementNode) { + const element = childNode; + const computedStyle = this.ownerDocument.defaultView.getComputedStyle(element); + const display = computedStyle.display; + + if (display === 'block') { + result += '\n'; } - } else if ( - childNode.nodeType === NodeTypeEnum.elementNode || - childNode.nodeType === NodeTypeEnum.textNode - ) { - result += childNode.textContent.replace(/[\n\r]/, ''); - } - if (childNode.nodeType === NodeTypeEnum.elementNode) { - const computedStyle = this.ownerDocument.defaultView.getComputedStyle( - childNode - ); - if (computedStyle.display === 'block') { + if (element.tagName !== 'SCRIPT' && element.tagName !== 'STYLE') { + result += element.innerText; + } + + if (display === 'block') { result += '\n'; } + } else if (childNode.nodeType === NodeTypeEnum.textNode) { + result += childNode.textContent.replace(/[\n\r]/, ''); } } diff --git a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts index b683ddba7..e597d344e 100644 --- a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts +++ b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts @@ -1,12 +1,16 @@ -import CSSStyleDeclarationCamelCaseKeys from './data/CSSStyleDeclarationCamelCaseKeys'; import CSSStyleDeclaration from '../../../src/css/declaration/CSSStyleDeclaration'; import Window from '../../../src/window/Window'; import IWindow from '../../../src/window/IWindow'; import IDocument from '../../../src/nodes/document/IDocument'; import IElement from '../../../src/nodes/element/IElement'; +import CSSStyleDeclarationDefaultValues from '../../../src/css/computed-style/config/CSSStyleDeclarationDefaultValues'; -function CAMEL_TO_KEBAB_CASE(string): string { - return string.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase(); +function KEBAB_TO_CAMEL_CASE(text: string): string { + const parts = text.split('-'); + for (let i = 0, max = parts.length; i < max; i++) { + parts[i] = i > 0 ? parts[i].charAt(0).toUpperCase() + parts[i].slice(1) : parts[i]; + } + return parts.join(''); } describe('CSSStyleDeclaration', () => { @@ -28,7 +32,7 @@ describe('CSSStyleDeclaration', () => { expect(declaration[0]).toBe('border'); expect(declaration[1]).toBe('border-radius'); - expect(declaration[2]).toBe('border-style'); + expect(declaration[2]).toBe('font-size'); expect(declaration[3]).toBe(undefined); }); @@ -41,37 +45,43 @@ describe('CSSStyleDeclaration', () => { expect(declaration[0]).toBe('border'); expect(declaration[1]).toBe('border-radius'); - expect(declaration[2]).toBe('border-style'); + expect(declaration[2]).toBe('font-size'); expect(declaration[3]).toBe(undefined); }); }); - for (const property of CSSStyleDeclarationCamelCaseKeys) { - describe(`get ${property}()`, () => { + for (const property of Object.keys(CSSStyleDeclarationDefaultValues)) { + const camelCaseProperty = KEBAB_TO_CAMEL_CASE(property); + describe(`get ${camelCaseProperty}()`, () => { it('Returns style property on element.', () => { const declaration = new CSSStyleDeclaration(element); - element.setAttribute('style', `${property}: test;`); - expect(declaration[property]).toBe('test'); + element.setAttribute( + 'style', + `${property}: ${CSSStyleDeclarationDefaultValues[property]};` + ); + expect(declaration[camelCaseProperty]).toBe(CSSStyleDeclarationDefaultValues[property]); }); it('Returns style property without element.', () => { const declaration = new CSSStyleDeclaration(); - declaration[property] = 'test'; - expect(declaration[property]).toBe('test'); + declaration[camelCaseProperty] = CSSStyleDeclarationDefaultValues[property]; + expect(declaration[camelCaseProperty]).toBe(CSSStyleDeclarationDefaultValues[property]); }); }); - describe(`set ${property}()`, () => { + describe(`set ${camelCaseProperty}()`, () => { it('Sets style property on element.', () => { const declaration = new CSSStyleDeclaration(element); - declaration[property] = 'test'; - expect(element.getAttribute('style')).toBe(`${CAMEL_TO_KEBAB_CASE(property)}: test;`); + declaration[camelCaseProperty] = CSSStyleDeclarationDefaultValues[property]; + expect(element.getAttribute('style')).toBe( + `${property}: ${CSSStyleDeclarationDefaultValues[property]};` + ); }); it('Sets style property without element.', () => { - const declaration = new CSSStyleDeclaration(element); - declaration[property] = 'test'; - expect(declaration[property]).toBe('test'); + const declaration = new CSSStyleDeclaration(); + declaration[camelCaseProperty] = CSSStyleDeclarationDefaultValues[property]; + expect(declaration[camelCaseProperty]).toBe(CSSStyleDeclarationDefaultValues[property]); }); }); } From 71c89f6b31c7682db8debb8d44f607f3e8e239bc Mon Sep 17 00:00:00 2001 From: Christian Wygoda Date: Thu, 28 Jul 2022 17:03:56 +0200 Subject: [PATCH 10/84] #554@patch: Use current timestamp in requestAnimationFrame callback. --- packages/happy-dom/src/window/Window.ts | 2 +- packages/happy-dom/test/window/Window.test.ts | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts index 95ede0e06..a16509930 100644 --- a/packages/happy-dom/src/window/Window.ts +++ b/packages/happy-dom/src/window/Window.ts @@ -549,7 +549,7 @@ export default class Window extends EventTarget implements IWindow { */ public requestAnimationFrame(callback: (timestamp: number) => void): NodeJS.Timeout { return this.setTimeout(() => { - callback(2); + callback(this.performance.now()); }); } diff --git a/packages/happy-dom/test/window/Window.test.ts b/packages/happy-dom/test/window/Window.test.ts index 5c2239e25..b0507c557 100644 --- a/packages/happy-dom/test/window/Window.test.ts +++ b/packages/happy-dom/test/window/Window.test.ts @@ -268,6 +268,13 @@ describe('Window', () => { const timeoutId = window.requestAnimationFrame(() => done()); expect(timeoutId.constructor.name).toBe('Timeout'); }); + + it('Calls passed callback with current time', (done) => { + window.requestAnimationFrame((now) => { + expect(Math.abs(now - window.performance.now())).toBeLessThan(100); + done(); + }); + }); }); describe('cancelAnimationFrame()', () => { From a67971a3024fefbcc3ecebc773ecde00382ceed8 Mon Sep 17 00:00:00 2001 From: Max Milton Date: Sun, 7 Aug 2022 16:57:11 +1000 Subject: [PATCH 11/84] #558@patch: Additional TreeWalker checks for parentNode. --- packages/happy-dom/src/tree-walker/TreeWalker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/happy-dom/src/tree-walker/TreeWalker.ts b/packages/happy-dom/src/tree-walker/TreeWalker.ts index bd056bd40..6a94b8ffe 100644 --- a/packages/happy-dom/src/tree-walker/TreeWalker.ts +++ b/packages/happy-dom/src/tree-walker/TreeWalker.ts @@ -125,7 +125,7 @@ export default class TreeWalker { * @returns Current node. */ public previousSibling(): INode { - if (this.currentNode !== this.root && this.currentNode) { + if (this.currentNode !== this.root && this.currentNode && this.currentNode.parentNode) { const siblings = this.currentNode.parentNode.childNodes; const index = siblings.indexOf(this.currentNode); @@ -149,7 +149,7 @@ export default class TreeWalker { * @returns Current node. */ public nextSibling(): INode { - if (this.currentNode !== this.root && this.currentNode) { + if (this.currentNode !== this.root && this.currentNode && this.currentNode.parentNode) { const siblings = this.currentNode.parentNode.childNodes; const index = siblings.indexOf(this.currentNode); From 6237ae8b3cedd31c78b32a6470d8e82a917fb06a Mon Sep 17 00:00:00 2001 From: David Ortner Date: Tue, 9 Aug 2022 01:11:15 +0200 Subject: [PATCH 12/84] #344@trivial: Continues on CSSStyleDeclaration. --- .../src/css/computed-style/ComputedStyle.ts | 54 - .../AbstractCSSStyleDeclaration.ts | 91 +- .../CSSStyleDeclarationDefaultValues.ts | 295 ----- .../CSSStyleDeclarationNodeDefaultValues.ts | 1095 ----------------- .../CSSStyleDeclarationPropertyParser.ts | 227 ---- .../CSSStyleDeclarationPropertyValidator.ts | 24 - .../CSSStyleDeclarationStyleParser.ts | 92 ++ .../CSSStyleDeclarationStylePropertyParser.ts | 474 +++++++ ...SStyleDeclarationStylePropertyValidator.ts | 84 ++ .../utilities/CSSStyleDeclarationUtility.ts | 153 --- .../declaration/CSSStyleDeclaration.test.ts | 2 +- .../data}/CSSStyleDeclarationDefaultValues.ts | 0 .../CSSStyleDeclarationNodeDefaultValues.ts | 0 13 files changed, 712 insertions(+), 1879 deletions(-) delete mode 100644 packages/happy-dom/src/css/computed-style/ComputedStyle.ts delete mode 100644 packages/happy-dom/src/css/declaration/config/CSSStyleDeclarationDefaultValues.ts delete mode 100644 packages/happy-dom/src/css/declaration/config/CSSStyleDeclarationNodeDefaultValues.ts delete mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyParser.ts delete mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValidator.ts create mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStyleParser.ts create mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyParser.ts create mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyValidator.ts delete mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationUtility.ts rename packages/happy-dom/{src/css/computed-style/config => test/css/declaration/data}/CSSStyleDeclarationDefaultValues.ts (100%) rename packages/happy-dom/{src/css/computed-style/config => test/css/declaration/data}/CSSStyleDeclarationNodeDefaultValues.ts (100%) diff --git a/packages/happy-dom/src/css/computed-style/ComputedStyle.ts b/packages/happy-dom/src/css/computed-style/ComputedStyle.ts deleted file mode 100644 index c0ae5e8fa..000000000 --- a/packages/happy-dom/src/css/computed-style/ComputedStyle.ts +++ /dev/null @@ -1,54 +0,0 @@ -import IElement from '../../nodes/element/IElement'; -import ComputedStylePropertyParser from './ComputedStylePropertyParser'; -import CSSStyleDeclarationDefaultValues from './config/CSSStyleDeclarationDefaultValues'; -import CSSStyleDeclarationNodeDefaultValues from './config/CSSStyleDeclarationNodeDefaultValues'; -import CSSStyleDeclaration from '../declaration/CSSStyleDeclaration'; -import CSSStyleDeclarationUtility from '../declaration/CSSStyleDeclarationUtility'; - -/** - * Computed styles. - */ -export default class ComputedStyle { - /** - * Converts style string to object. - * - * @param element Element. - * @returns Style object. - */ - public static getComputedStyle(element: IElement): CSSStyleDeclaration { - const cssStyleDeclaration = new CSSStyleDeclaration(); - const styles = this.getStyles(element); - - for (const key of Object.keys(styles)) { - cssStyleDeclaration.setProperty(key, styles[key]); - } - - cssStyleDeclaration._readonly = true; - - return cssStyleDeclaration; - } - - /** - * Returns property styles for element. - * - * @param element Element. - * @returns Styles. - */ - private static getStyles(element: IElement): { [k: string]: string } { - const styles = {}; - - if (element['_attributes']['style'] && element['_attributes']['style'].value) { - const styleProperty = CSSStyleDeclarationUtility.styleStringToObject( - element['_attributes']['style'].value - ); - for (const key of Object.keys(styleProperty)) { - Object.assign(styles, ComputedStylePropertyParser.parseProperty(key, styleProperty[key])); - } - } - return Object.assign( - CSSStyleDeclarationDefaultValues, - CSSStyleDeclarationNodeDefaultValues[element.tagName], - styles - ); - } -} diff --git a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts index b6f081261..1d483b184 100644 --- a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts +++ b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts @@ -3,9 +3,9 @@ import Attr from '../../nodes/attr/Attr'; import CSSRule from '../CSSRule'; import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum'; import DOMException from '../../exception/DOMException'; -import CSSStyleDeclarationUtility from './utilities/CSSStyleDeclarationUtility'; -import CSSStyleDeclarationDefaultValues from './config/CSSStyleDeclarationDefaultValues'; +import CSSStyleDeclarationStyleParser from './utilities/CSSStyleDeclarationStyleParser'; import ICSSStyleDeclarationProperty from './ICSSStyleDeclarationProperty'; +import CSSStyleDeclarationStylePropertyParser from './utilities/CSSStyleDeclarationStylePropertyParser'; /** * CSS Style Declaration. @@ -35,7 +35,9 @@ export default abstract class AbstractCSSStyleDeclaration { */ public get length(): number { if (this._ownerElement) { - return CSSStyleDeclarationUtility.getElementStyleProperties(this._ownerElement).length; + return Object.keys( + CSSStyleDeclarationStyleParser.getElementStyleProperties(this._ownerElement, this._computed) + ).length; } return Object.keys(this._styles).length; @@ -52,12 +54,12 @@ export default abstract class AbstractCSSStyleDeclaration { return ''; } - return CSSStyleDeclarationUtility.styleObjectToString( - CSSStyleDeclarationUtility.getElementStyle(this._ownerElement) + return CSSStyleDeclarationStyleParser.getStyleString( + CSSStyleDeclarationStyleParser.getElementStyleProperties(this._ownerElement, this._computed) ); } - return CSSStyleDeclarationUtility.styleObjectToString(this._styles); + return CSSStyleDeclarationStyleParser.getStyleString(this._styles); } /** @@ -74,8 +76,8 @@ export default abstract class AbstractCSSStyleDeclaration { } if (this._ownerElement) { - const parsed = CSSStyleDeclarationUtility.styleObjectToString( - CSSStyleDeclarationUtility.styleStringToObject(cssText) + const parsed = CSSStyleDeclarationStyleParser.getStyleString( + CSSStyleDeclarationStyleParser.getStyleProperties(cssText) ); if (!parsed) { delete this._ownerElement['_attributes']['style']; @@ -89,7 +91,7 @@ export default abstract class AbstractCSSStyleDeclaration { this._ownerElement['_attributes']['style'].value = parsed; } } else { - this._styles = CSSStyleDeclarationUtility.styleStringToObject(cssText); + this._styles = CSSStyleDeclarationStyleParser.getStyleProperties(cssText); } } @@ -101,11 +103,13 @@ export default abstract class AbstractCSSStyleDeclaration { */ public item(index: number): string { if (this._ownerElement) { - if (this._computed) { - return Object.keys(CSSStyleDeclarationDefaultValues)[index] || ''; - } return ( - Object.keys(CSSStyleDeclarationUtility.getElementStyle(this._ownerElement))[index] || '' + Object.keys( + CSSStyleDeclarationStyleParser.getElementStyleProperties( + this._ownerElement, + this._computed + ) + )[index] || '' ); } return Object.keys(this._styles)[index] || ''; @@ -134,8 +138,6 @@ export default abstract class AbstractCSSStyleDeclaration { return; } - const important = priority ? ' !important' : ''; - if (!value) { this.removeProperty(propertyName); } else if (this._ownerElement) { @@ -145,16 +147,31 @@ export default abstract class AbstractCSSStyleDeclaration { this._ownerElement['_attributes']['style'].name = 'style'; } - const styleObject = CSSStyleDeclarationUtility.styleStringToObject( - this._ownerElement['_attributes']['style'].value + const elementStyleProperties = CSSStyleDeclarationStyleParser.getElementStyleProperties( + this._ownerElement, + this._computed ); - styleObject[propertyName] = value + important; + Object.assign( + elementStyleProperties, + CSSStyleDeclarationStylePropertyParser.getValidProperties({ + name: propertyName, + value, + important: !!priority + }) + ); this._ownerElement['_attributes']['style'].value = - CSSStyleDeclarationUtility.styleObjectToString(styleObject); + CSSStyleDeclarationStyleParser.getStyleString(elementStyleProperties); } else { - this._styles[propertyName] = value + important; + Object.assign( + this._styles, + CSSStyleDeclarationStylePropertyParser.getValidProperties({ + name: propertyName, + value, + important: !!priority + }) + ); } } @@ -175,13 +192,18 @@ export default abstract class AbstractCSSStyleDeclaration { if (this._ownerElement) { if (this._ownerElement['_attributes']['style']) { - const styleObject = CSSStyleDeclarationUtility.styleStringToObject( - this._ownerElement['_attributes']['style'].value + const elementStyleProperties = CSSStyleDeclarationStyleParser.getElementStyleProperties( + this._ownerElement, + this._computed ); + const propertiesToRemove = + CSSStyleDeclarationStylePropertyParser.getRelatedPropertyNames(propertyName); - delete styleObject[propertyName]; + for (const property of Object.keys(propertiesToRemove)) { + delete elementStyleProperties[property]; + } - const styleString = CSSStyleDeclarationUtility.styleObjectToString(styleObject); + const styleString = CSSStyleDeclarationStyleParser.getStyleString(elementStyleProperties); if (styleString) { this._ownerElement['_attributes']['style'].value = styleString; @@ -190,7 +212,12 @@ export default abstract class AbstractCSSStyleDeclaration { } } } else { - delete this._styles[propertyName]; + const propertiesToRemove = + CSSStyleDeclarationStylePropertyParser.getRelatedPropertyNames(propertyName); + + for (const property of Object.keys(propertiesToRemove)) { + delete this._styles[property]; + } } } @@ -202,11 +229,15 @@ export default abstract class AbstractCSSStyleDeclaration { */ public getPropertyValue(propertyName: string): string { if (this._ownerElement) { - const elementStyle = CSSStyleDeclarationUtility.getElementStyle(this._ownerElement); - const value = elementStyle[propertyName]; - return value ? value.replace(' !important', '') : ''; + const elementStyleProperties = CSSStyleDeclarationStyleParser.getElementStyleProperties( + this._ownerElement, + this._computed + ); + return CSSStyleDeclarationStylePropertyParser.getPropertyValue( + elementStyleProperties, + propertyName + ); } - - return this._styles[propertyName] ? this._styles[propertyName].replace(' !important', '') : ''; + return CSSStyleDeclarationStylePropertyParser.getPropertyValue(this._styles, propertyName); } } diff --git a/packages/happy-dom/src/css/declaration/config/CSSStyleDeclarationDefaultValues.ts b/packages/happy-dom/src/css/declaration/config/CSSStyleDeclarationDefaultValues.ts deleted file mode 100644 index 335e05b30..000000000 --- a/packages/happy-dom/src/css/declaration/config/CSSStyleDeclarationDefaultValues.ts +++ /dev/null @@ -1,295 +0,0 @@ -export default { - 'accent-color': 'auto', - 'align-content': 'normal', - 'align-items': 'normal', - 'align-self': 'auto', - 'alignment-baseline': 'auto', - 'animation-delay': '0s', - 'animation-direction': 'normal', - 'animation-duration': '0s', - 'animation-fill-mode': 'none', - 'animation-iteration-count': '1', - 'animation-name': 'none', - 'animation-play-state': 'running', - 'animation-timing-function': 'ease', - 'app-region': 'none', - appearance: 'none', - 'backdrop-filter': 'none', - 'backface-visibility': 'visible', - 'background-attachment': 'scroll', - 'background-blend-mode': 'normal', - 'background-clip': 'border-box', - 'background-color': 'rgba(0, 0, 0, 0)', - 'background-image': 'none', - 'background-origin': 'padding-box', - 'background-position': '0% 0%', - 'background-repeat': 'repeat', - 'background-size': 'auto', - 'baseline-shift': '0px', - 'block-size': 'auto', - 'border-block-end-color': 'rgb(0, 0, 0)', - 'border-block-end-style': 'none', - 'border-block-end-width': '0px', - 'border-block-start-color': 'rgb(0, 0, 0)', - 'border-block-start-style': 'none', - 'border-block-start-width': '0px', - 'border-bottom-color': 'rgb(0, 0, 0)', - 'border-bottom-left-radius': '0px', - 'border-bottom-right-radius': '0px', - 'border-bottom-style': 'none', - 'border-bottom-width': '0px', - 'border-collapse': 'separate', - 'border-end-end-radius': '0px', - 'border-end-start-radius': '0px', - 'border-image-outset': '0', - 'border-image-repeat': 'stretch', - 'border-image-slice': '100%', - 'border-image-source': 'none', - 'border-image-width': '1', - 'border-inline-end-color': 'rgb(0, 0, 0)', - 'border-inline-end-style': 'none', - 'border-inline-end-width': '0px', - 'border-inline-start-color': 'rgb(0, 0, 0)', - 'border-inline-start-style': 'none', - 'border-inline-start-width': '0px', - 'border-left-color': 'rgb(0, 0, 0)', - 'border-left-style': 'none', - 'border-left-width': '0px', - 'border-right-color': 'rgb(0, 0, 0)', - 'border-right-style': 'none', - 'border-right-width': '0px', - 'border-start-end-radius': '0px', - 'border-start-start-radius': '0px', - 'border-top-color': 'rgb(0, 0, 0)', - 'border-top-left-radius': '0px', - 'border-top-right-radius': '0px', - 'border-top-style': 'none', - 'border-top-width': '0px', - bottom: 'auto', - 'box-shadow': 'none', - 'box-sizing': 'content-box', - 'break-after': 'auto', - 'break-before': 'auto', - 'break-inside': 'auto', - 'buffered-rendering': 'auto', - 'caption-side': 'top', - 'caret-color': 'rgb(0, 0, 0)', - clear: 'none', - clip: 'auto', - 'clip-path': 'none', - 'clip-rule': 'nonzero', - color: 'rgb(0, 0, 0)', - 'color-interpolation': 'srgb', - 'color-interpolation-filters': 'linearrgb', - 'color-rendering': 'auto', - 'column-count': 'auto', - 'column-gap': 'normal', - 'column-rule-color': 'rgb(0, 0, 0)', - 'column-rule-style': 'none', - 'column-rule-width': '0px', - 'column-span': 'none', - 'column-width': 'auto', - 'contain-intrinsic-block-size': 'none', - 'contain-intrinsic-height': 'none', - 'contain-intrinsic-inline-size': 'none', - 'contain-intrinsic-size': 'none', - 'contain-intrinsic-width': 'none', - content: 'normal', - cursor: 'auto', - cx: '0px', - cy: '0px', - d: 'none', - direction: 'ltr', - display: 'inline', - 'dominant-baseline': 'auto', - 'empty-cells': 'show', - fill: 'rgb(0, 0, 0)', - 'fill-opacity': '1', - 'fill-rule': 'nonzero', - filter: 'none', - 'flex-basis': 'auto', - 'flex-direction': 'row', - 'flex-grow': '0', - 'flex-shrink': '1', - 'flex-wrap': 'nowrap', - float: 'none', - 'flood-color': 'rgb(0, 0, 0)', - 'flood-opacity': '1', - 'font-family': 'Roboto, system-ui, sans-serif', - 'font-kerning': 'auto', - 'font-optical-sizing': 'auto', - 'font-palette': 'normal', - 'font-size': '13px', - 'font-stretch': '100%', - 'font-style': 'normal', - 'font-synthesis-small-caps': 'auto', - 'font-synthesis-style': 'auto', - 'font-synthesis-weight': 'auto', - 'font-variant': 'normal', - 'font-variant-caps': 'normal', - 'font-variant-east-asian': 'normal', - 'font-variant-ligatures': 'normal', - 'font-variant-numeric': 'normal', - 'font-weight': '400', - 'grid-auto-columns': 'auto', - 'grid-auto-flow': 'row', - 'grid-auto-rows': 'auto', - 'grid-column-end': 'auto', - 'grid-column-start': 'auto', - 'grid-row-end': 'auto', - 'grid-row-start': 'auto', - 'grid-template-areas': 'none', - 'grid-template-columns': 'none', - 'grid-template-rows': 'none', - height: 'auto', - hyphens: 'manual', - 'image-orientation': 'from-image', - 'image-rendering': 'auto', - 'inline-size': 'auto', - 'inset-block-end': 'auto', - 'inset-block-start': 'auto', - 'inset-inline-end': 'auto', - 'inset-inline-start': 'auto', - isolation: 'auto', - 'justify-content': 'normal', - 'justify-items': 'normal', - 'justify-self': 'auto', - left: 'auto', - 'letter-spacing': 'normal', - 'lighting-color': 'rgb(255, 255, 255)', - 'line-break': 'auto', - 'line-height': 'normal', - 'list-style-image': 'none', - 'list-style-position': 'outside', - 'list-style-type': 'disc', - 'margin-block-end': '0px', - 'margin-block-start': '0px', - 'margin-bottom': '0px', - 'margin-inline-end': '0px', - 'margin-inline-start': '0px', - 'margin-left': '0px', - 'margin-right': '0px', - 'margin-top': '0px', - 'marker-end': 'none', - 'marker-mid': 'none', - 'marker-start': 'none', - 'mask-type': 'luminance', - 'max-block-size': 'none', - 'max-height': 'none', - 'max-inline-size': 'none', - 'max-width': 'none', - 'min-block-size': '0px', - 'min-height': '0px', - 'min-inline-size': '0px', - 'min-width': '0px', - 'mix-blend-mode': 'normal', - 'object-fit': 'fill', - 'object-position': '50% 50%', - 'offset-distance': '0px', - 'offset-path': 'none', - 'offset-rotate': 'auto 0deg', - opacity: '1', - order: '0', - orphans: '2', - 'outline-color': 'rgb(0, 0, 0)', - 'outline-offset': '0px', - 'outline-style': 'none', - 'outline-width': '0px', - 'overflow-anchor': 'auto', - 'overflow-clip-margin': '0px', - 'overflow-wrap': 'normal', - 'overflow-x': 'visible', - 'overflow-y': 'visible', - 'overscroll-behavior-block': 'auto', - 'overscroll-behavior-inline': 'auto', - 'padding-block-end': '0px', - 'padding-block-start': '0px', - 'padding-bottom': '0px', - 'padding-inline-end': '0px', - 'padding-inline-start': '0px', - 'padding-left': '0px', - 'padding-right': '0px', - 'padding-top': '0px', - 'paint-order': 'normal', - perspective: 'none', - 'perspective-origin': '0px 0px', - 'pointer-events': 'auto', - position: 'static', - r: '0px', - resize: 'none', - right: 'auto', - 'row-gap': 'normal', - 'ruby-position': 'over', - rx: 'auto', - ry: 'auto', - 'scroll-behavior': 'auto', - 'scroll-margin-block-end': '0px', - 'scroll-margin-block-start': '0px', - 'scroll-margin-inline-end': '0px', - 'scroll-margin-inline-start': '0px', - 'scroll-padding-block-end': 'auto', - 'scroll-padding-block-start': 'auto', - 'scroll-padding-inline-end': 'auto', - 'scroll-padding-inline-start': 'auto', - 'scrollbar-gutter': 'auto', - 'shape-image-threshold': '0', - 'shape-margin': '0px', - 'shape-outside': 'none', - 'shape-rendering': 'auto', - speak: 'normal', - 'stop-color': 'rgb(0, 0, 0)', - 'stop-opacity': '1', - stroke: 'none', - 'stroke-dasharray': 'none', - 'stroke-dashoffset': '0px', - 'stroke-linecap': 'butt', - 'stroke-linejoin': 'miter', - 'stroke-miterlimit': '4', - 'stroke-opacity': '1', - 'stroke-width': '1px', - 'tab-size': '8', - 'table-layout': 'auto', - 'text-align': 'start', - 'text-align-last': 'auto', - 'text-anchor': 'start', - 'text-decoration': 'none solid rgb(0, 0, 0)', - 'text-decoration-color': 'rgb(0, 0, 0)', - 'text-decoration-line': 'none', - 'text-decoration-skip-ink': 'auto', - 'text-decoration-style': 'solid', - 'text-emphasis-color': 'rgb(0, 0, 0)', - 'text-emphasis-position': 'over', - 'text-emphasis-style': 'none', - 'text-indent': '0px', - 'text-overflow': 'clip', - 'text-rendering': 'auto', - 'text-shadow': 'none', - 'text-size-adjust': 'auto', - 'text-transform': 'none', - 'text-underline-position': 'auto', - top: 'auto', - 'touch-action': 'auto', - transform: 'none', - 'transform-origin': '0px 0px', - 'transform-style': 'flat', - 'transition-delay': '0s', - 'transition-duration': '0s', - 'transition-property': 'all', - 'transition-timing-function': 'ease', - 'unicode-bidi': 'normal', - 'user-select': 'auto', - 'vector-effect': 'none', - 'vertical-align': 'baseline', - visibility: 'visible', - 'white-space': 'normal', - widows: '2', - width: 'auto', - 'will-change': 'auto', - 'word-break': 'normal', - 'word-spacing': '0px', - 'writing-mode': 'horizontal-tb', - x: '0px', - y: '0px', - 'z-index': 'auto', - zoom: '1' -}; diff --git a/packages/happy-dom/src/css/declaration/config/CSSStyleDeclarationNodeDefaultValues.ts b/packages/happy-dom/src/css/declaration/config/CSSStyleDeclarationNodeDefaultValues.ts deleted file mode 100644 index 63df45eb1..000000000 --- a/packages/happy-dom/src/css/declaration/config/CSSStyleDeclarationNodeDefaultValues.ts +++ /dev/null @@ -1,1095 +0,0 @@ -export default { - A: {}, - ABBR: {}, - ADDRESS: { - 'block-size': '0px', - display: 'block', - 'font-style': 'italic', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - AREA: {}, - ARTICLE: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - ASIDE: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - AUDIO: { - 'block-size': '54px', - display: 'none', - height: '54px', - 'inline-size': '300px', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%', - width: '300px' - }, - B: { - 'font-weight': '700' - }, - BASE: { - display: 'none', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - BDI: { - 'unicode-bidi': 'isolate' - }, - BDO: { - 'unicode-bidi': 'bidi-override' - }, - BLOCKQUAOTE: {}, - BODY: { - 'background-color': 'rgb(255, 255, 255)', - 'block-size': '0px', - display: 'block', - 'font-size': '10.5625px', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - TEMPLATE: { - display: 'none', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - FORM: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - INPUT: { - appearance: 'auto', - 'background-color': 'rgb(255, 255, 255)', - 'block-size': '15.5px', - 'border-block-end-color': 'rgb(118, 118, 118)', - 'border-block-end-style': 'inset', - 'border-block-end-width': '2px', - 'border-block-start-color': 'rgb(118, 118, 118)', - 'border-block-start-style': 'inset', - 'border-block-start-width': '2px', - 'border-bottom-color': 'rgb(118, 118, 118)', - 'border-bottom-style': 'inset', - 'border-bottom-width': '2px', - 'border-inline-end-color': 'rgb(118, 118, 118)', - 'border-inline-end-style': 'inset', - 'border-inline-end-width': '2px', - 'border-inline-start-color': 'rgb(118, 118, 118)', - 'border-inline-start-style': 'inset', - 'border-inline-start-width': '2px', - 'border-left-color': 'rgb(118, 118, 118)', - 'border-left-style': 'inset', - 'border-left-width': '2px', - 'border-right-color': 'rgb(118, 118, 118)', - 'border-right-style': 'inset', - 'border-right-width': '2px', - 'border-top-color': 'rgb(118, 118, 118)', - 'border-top-style': 'inset', - 'border-top-width': '2px', - cursor: 'text', - display: 'inline-block', - 'font-family': 'Arial', - 'font-size': '13.3333px', - height: '15.5px', - 'inline-size': '139px', - 'padding-block-end': '1px', - 'padding-block-start': '1px', - 'padding-bottom': '1px', - 'padding-inline-end': '2px', - 'padding-inline-start': '2px', - 'padding-left': '2px', - 'padding-right': '2px', - 'padding-top': '1px', - 'perspective-origin': '73.5px 10.75px', - 'transform-origin': '73.5px 10.75px', - width: '139px' - }, - TEXTAREA: { - appearance: 'auto', - 'background-color': 'rgb(255, 255, 255)', - 'block-size': '31px', - 'border-block-end-color': 'rgb(118, 118, 118)', - 'border-block-end-style': 'solid', - 'border-block-end-width': '1px', - 'border-block-start-color': 'rgb(118, 118, 118)', - 'border-block-start-style': 'solid', - 'border-block-start-width': '1px', - 'border-bottom-color': 'rgb(118, 118, 118)', - 'border-bottom-style': 'solid', - 'border-bottom-width': '1px', - 'border-inline-end-color': 'rgb(118, 118, 118)', - 'border-inline-end-style': 'solid', - 'border-inline-end-width': '1px', - 'border-inline-start-color': 'rgb(118, 118, 118)', - 'border-inline-start-style': 'solid', - 'border-inline-start-width': '1px', - 'border-left-color': 'rgb(118, 118, 118)', - 'border-left-style': 'solid', - 'border-left-width': '1px', - 'border-right-color': 'rgb(118, 118, 118)', - 'border-right-style': 'solid', - 'border-right-width': '1px', - 'border-top-color': 'rgb(118, 118, 118)', - 'border-top-style': 'solid', - 'border-top-width': '1px', - cursor: 'text', - display: 'inline-block', - 'font-family': 'monospace', - 'font-size': '13.3333px', - height: '31px', - 'inline-size': '176px', - 'overflow-wrap': 'break-word', - 'overflow-x': 'auto', - 'overflow-y': 'auto', - 'padding-block-end': '2px', - 'padding-block-start': '2px', - 'padding-bottom': '2px', - 'padding-inline-end': '2px', - 'padding-inline-start': '2px', - 'padding-left': '2px', - 'padding-right': '2px', - 'padding-top': '2px', - 'perspective-origin': '91px 18.5px', - resize: 'both', - 'transform-origin': '91px 18.5px', - 'white-space': 'pre-wrap', - width: '176px' - }, - SCRIPT: { - display: 'none', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - IMG: { - 'block-size': '0px', - height: '0px', - 'inline-size': '0px', - width: '0px' - }, - LINK: { - display: 'none', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - STYLE: { - display: 'none', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - LABEL: { - cursor: 'default' - }, - SLOT: { - display: 'contents', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - SVG: {}, - CIRCLE: {}, - ELLIPSE: {}, - LINE: {}, - PATH: {}, - POLYGON: {}, - POLYLINE: {}, - RECT: {}, - STOP: {}, - USE: {}, - META: { - display: 'none', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - BLOCKQUOTE: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1432px', - 'margin-block-end': '13px', - 'margin-block-start': '13px', - 'margin-bottom': '13px', - 'margin-inline-end': '40px', - 'margin-inline-start': '40px', - 'margin-left': '40px', - 'margin-right': '40px', - 'margin-top': '13px', - 'perspective-origin': '716px 0px', - 'transform-origin': '716px 0px', - width: '1432px' - }, - BR: {}, - BUTTON: { - 'align-items': 'flex-start', - appearance: 'auto', - 'background-color': 'rgb(239, 239, 239)', - 'block-size': '6px', - 'border-block-end-color': 'rgb(118, 118, 118)', - 'border-block-end-style': 'outset', - 'border-block-end-width': '2px', - 'border-block-start-color': 'rgb(118, 118, 118)', - 'border-block-start-style': 'outset', - 'border-block-start-width': '2px', - 'border-bottom-color': 'rgb(118, 118, 118)', - 'border-bottom-style': 'outset', - 'border-bottom-width': '2px', - 'border-inline-end-color': 'rgb(118, 118, 118)', - 'border-inline-end-style': 'outset', - 'border-inline-end-width': '2px', - 'border-inline-start-color': 'rgb(118, 118, 118)', - 'border-inline-start-style': 'outset', - 'border-inline-start-width': '2px', - 'border-left-color': 'rgb(118, 118, 118)', - 'border-left-style': 'outset', - 'border-left-width': '2px', - 'border-right-color': 'rgb(118, 118, 118)', - 'border-right-style': 'outset', - 'border-right-width': '2px', - 'border-top-color': 'rgb(118, 118, 118)', - 'border-top-style': 'outset', - 'border-top-width': '2px', - 'box-sizing': 'border-box', - cursor: 'default', - display: 'inline-block', - 'font-size': '13.3333px', - height: '6px', - 'inline-size': '16px', - 'padding-block-end': '1px', - 'padding-block-start': '1px', - 'padding-bottom': '1px', - 'padding-inline-end': '6px', - 'padding-inline-start': '6px', - 'padding-left': '6px', - 'padding-right': '6px', - 'padding-top': '1px', - 'perspective-origin': '8px 3px', - 'text-align': 'center', - 'transform-origin': '8px 3px', - width: '16px' - }, - CANVAS: { - 'block-size': '150px', - height: '150px', - 'inline-size': '300px', - 'perspective-origin': '150px 75px', - 'transform-origin': '150px 75px', - width: '300px' - }, - CAPTION: { - 'block-size': '0px', - display: 'table-caption', - height: '0px', - 'inline-size': '0px', - 'text-align': '-webkit-center', - width: '0px' - }, - CITE: { - 'font-style': 'italic' - }, - CODE: { - 'font-family': 'monospace', - 'font-size': '10.5625px' - }, - COL: { - 'block-size': '0px', - display: 'table-column', - height: '0px', - 'inline-size': '0px', - width: '0px' - }, - COLGROUP: { - 'block-size': '0px', - display: 'table-column-group', - height: '0px', - 'inline-size': '0px', - width: '0px' - }, - DATA: {}, - DATALIST: { - display: 'none', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - DD: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1472px', - 'margin-inline-start': '40px', - 'margin-left': '40px', - 'perspective-origin': '736px 0px', - 'transform-origin': '736px 0px', - width: '1472px' - }, - DEL: { - 'text-decoration': 'line-through solid rgb(0, 0, 0)', - 'text-decoration-line': 'line-through', - '-webkit-text-decorations-in-effect': 'line-through' - }, - DETAILS: { - 'block-size': '15px', - display: 'block', - height: '15px', - 'inline-size': '1000px', - 'perspective-origin': '756px 7.5px', - 'transform-origin': '756px 7.5px', - width: '1000px' - }, - DFN: { - 'font-style': 'italic' - }, - DIALOG: { - 'background-color': 'rgb(255, 255, 255)', - 'block-size': 'fit-content', - 'border-block-end-style': 'solid', - 'border-block-end-width': '1.5px', - 'border-block-start-style': 'solid', - 'border-block-start-width': '1.5px', - 'border-bottom-style': 'solid', - 'border-bottom-width': '1.5px', - 'border-inline-end-style': 'solid', - 'border-inline-end-width': '1.5px', - 'border-inline-start-style': 'solid', - 'border-inline-start-width': '1.5px', - 'border-left-style': 'solid', - 'border-left-width': '1.5px', - 'border-right-style': 'solid', - 'border-right-width': '1.5px', - 'border-top-style': 'solid', - 'border-top-width': '1.5px', - display: 'none', - height: 'fit-content', - 'inline-size': 'fit-content', - 'inset-inline-end': '0px', - 'inset-inline-start': '0px', - left: '0px', - 'margin-block-end': 'auto', - 'margin-block-start': 'auto', - 'margin-bottom': 'auto', - 'margin-inline-end': 'auto', - 'margin-inline-start': 'auto', - 'margin-left': 'auto', - 'margin-right': 'auto', - 'margin-top': 'auto', - 'padding-block-end': '13px', - 'padding-block-start': '13px', - 'padding-bottom': '13px', - 'padding-inline-end': '13px', - 'padding-inline-start': '13px', - 'padding-left': '13px', - 'padding-right': '13px', - 'padding-top': '13px', - 'perspective-origin': '50% 50%', - position: 'absolute', - right: '0px', - 'transform-origin': '50% 50%', - width: 'fit-content' - }, - DIV: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - DL: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'margin-block-end': '13px', - 'margin-block-start': '13px', - 'margin-bottom': '13px', - 'margin-top': '13px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - DT: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - EM: { - 'font-style': 'italic' - }, - EMBED: { - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - FIELDSET: { - 'block-size': '0px', - 'border-block-end-color': 'rgb(192, 192, 192)', - 'border-block-end-style': 'groove', - 'border-block-end-width': '2px', - 'border-block-start-color': 'rgb(192, 192, 192)', - 'border-block-start-style': 'groove', - 'border-block-start-width': '2px', - 'border-bottom-color': 'rgb(192, 192, 192)', - 'border-bottom-style': 'groove', - 'border-bottom-width': '2px', - 'border-inline-end-color': 'rgb(192, 192, 192)', - 'border-inline-end-style': 'groove', - 'border-inline-end-width': '2px', - 'border-inline-start-color': 'rgb(192, 192, 192)', - 'border-inline-start-style': 'groove', - 'border-inline-start-width': '2px', - 'border-left-color': 'rgb(192, 192, 192)', - 'border-left-style': 'groove', - 'border-left-width': '2px', - 'border-right-color': 'rgb(192, 192, 192)', - 'border-right-style': 'groove', - 'border-right-width': '2px', - 'border-top-color': 'rgb(192, 192, 192)', - 'border-top-style': 'groove', - 'border-top-width': '2px', - display: 'block', - height: '0px', - 'inline-size': '1484.5px', - 'margin-inline-end': '2px', - 'margin-inline-start': '2px', - 'margin-left': '2px', - 'margin-right': '2px', - 'min-inline-size': 'min-content', - 'min-width': 'min-content', - 'padding-block-end': '8.125px', - 'padding-block-start': '4.55px', - 'padding-bottom': '8.125px', - 'padding-inline-end': '9.75px', - 'padding-inline-start': '9.75px', - 'padding-left': '9.75px', - 'padding-right': '9.75px', - 'padding-top': '4.55px', - 'perspective-origin': '754px 8.33594px', - 'transform-origin': '754px 8.33594px', - width: '1484.5px' - }, - FIGCAPTION: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - FIGURE: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1432px', - 'margin-block-end': '13px', - 'margin-block-start': '13px', - 'margin-bottom': '13px', - 'margin-inline-end': '40px', - 'margin-inline-start': '40px', - 'margin-left': '40px', - 'margin-right': '40px', - 'margin-top': '13px', - 'perspective-origin': '716px 0px', - 'transform-origin': '716px 0px', - width: '1432px' - }, - FOOTER: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - H1: { - 'block-size': '0px', - display: 'block', - 'font-size': '26px', - 'font-weight': '700', - height: '0px', - 'inline-size': '1000px', - 'margin-block-end': '17.42px', - 'margin-block-start': '17.42px', - 'margin-bottom': '17.42px', - 'margin-top': '17.42px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - H2: { - 'block-size': '0px', - display: 'block', - 'font-size': '19.5px', - 'font-weight': '700', - height: '0px', - 'inline-size': '1000px', - 'margin-block-end': '16.185px', - 'margin-block-start': '16.185px', - 'margin-bottom': '16.185px', - 'margin-top': '16.185px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - H3: { - 'block-size': '0px', - display: 'block', - 'font-size': '15.21px', - 'font-weight': '700', - height: '0px', - 'inline-size': '1000px', - 'margin-block-end': '15.21px', - 'margin-block-start': '15.21px', - 'margin-bottom': '15.21px', - 'margin-top': '15.21px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - H4: { - 'block-size': '0px', - display: 'block', - 'font-weight': '700', - height: '0px', - 'inline-size': '1000px', - 'margin-block-end': '17.29px', - 'margin-block-start': '17.29px', - 'margin-bottom': '17.29px', - 'margin-top': '17.29px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - H5: { - 'block-size': '0px', - display: 'block', - 'font-size': '10.79px', - 'font-weight': '700', - height: '0px', - 'inline-size': '1000px', - 'margin-block-end': '18.0193px', - 'margin-block-start': '18.0193px', - 'margin-bottom': '18.0193px', - 'margin-top': '18.0193px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - H6: { - 'block-size': '0px', - display: 'block', - 'font-size': '8.71px', - 'font-weight': '700', - height: '0px', - 'inline-size': '1000px', - 'margin-block-end': '20.2943px', - 'margin-block-start': '20.2943px', - 'margin-bottom': '20.2943px', - 'margin-top': '20.2943px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - HEAD: { - display: 'none', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - HEADER: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - HGROUP: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - HR: { - 'block-size': '0px', - 'border-block-end-style': 'inset', - 'border-block-end-width': '1px', - 'border-block-start-style': 'inset', - 'border-block-start-width': '1px', - 'border-bottom-style': 'inset', - 'border-bottom-width': '1px', - 'border-inline-end-style': 'inset', - 'border-inline-end-width': '1px', - 'border-inline-start-style': 'inset', - 'border-inline-start-width': '1px', - 'border-left-style': 'inset', - 'border-left-width': '1px', - 'border-right-style': 'inset', - 'border-right-width': '1px', - 'border-top-style': 'inset', - 'border-top-width': '1px', - display: 'block', - height: '0px', - 'inline-size': '1510px', - 'margin-block-end': '6.5px', - 'margin-block-start': '6.5px', - 'margin-bottom': '6.5px', - 'margin-top': '6.5px', - 'overflow-x': 'hidden', - 'overflow-y': 'hidden', - 'perspective-origin': '756px 1px', - 'transform-origin': '756px 1px', - 'unicode-bidi': 'isolate', - width: '1510px' - }, - HTML: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - I: { - 'font-style': 'italic' - }, - IFRAME: { - 'block-size': '150px', - 'border-block-end-style': 'inset', - 'border-block-end-width': '2px', - 'border-block-start-style': 'inset', - 'border-block-start-width': '2px', - 'border-bottom-style': 'inset', - 'border-bottom-width': '2px', - 'border-inline-end-style': 'inset', - 'border-inline-end-width': '2px', - 'border-inline-start-style': 'inset', - 'border-inline-start-width': '2px', - 'border-left-style': 'inset', - 'border-left-width': '2px', - 'border-right-style': 'inset', - 'border-right-width': '2px', - 'border-top-style': 'inset', - 'border-top-width': '2px', - height: '150px', - 'inline-size': '300px', - 'perspective-origin': '152px 77px', - 'transform-origin': '152px 77px', - width: '300px' - }, - INS: { - 'text-decoration': 'underline solid rgb(0, 0, 0)', - 'text-decoration-line': 'underline', - '-webkit-text-decorations-in-effect': 'underline' - }, - KBD: { - 'font-family': 'monospace', - 'font-size': '10.5625px' - }, - LEGEND: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1508px', - 'padding-inline-end': '2px', - 'padding-inline-start': '2px', - 'padding-left': '2px', - 'padding-right': '2px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1508px' - }, - LI: { - 'block-size': '15px', - display: 'list-item', - height: '15px', - 'inline-size': '1000px', - 'perspective-origin': '756px 7.5px', - 'text-align': 'left', - 'transform-origin': '756px 7.5px', - width: '1000px' - }, - MAIN: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - MAP: {}, - MARK: { - 'background-color': 'rgb(255, 255, 0)' - }, - MATH: {}, - MENU: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1472px', - 'margin-block-end': '13px', - 'margin-block-start': '13px', - 'margin-bottom': '13px', - 'margin-top': '13px', - 'padding-inline-start': '40px', - 'padding-left': '40px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1472px' - }, - MENUITEM: {}, - METER: { - appearance: 'auto', - 'block-size': '13px', - 'box-sizing': 'border-box', - display: 'inline-block', - height: '13px', - 'inline-size': '65px', - 'perspective-origin': '32.5px 6.5px', - 'transform-origin': '32.5px 6.5px', - 'vertical-align': '-2.6px', - width: '65px' - }, - NAV: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - NOSCRIPT: { - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - OBJECT: { - 'block-size': '150px', - height: '150px', - 'inline-size': '300px', - 'perspective-origin': '150px 75px', - 'transform-origin': '150px 75px', - width: '300px' - }, - OL: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1472px', - 'list-style-type': 'decimal', - 'margin-block-end': '13px', - 'margin-block-start': '13px', - 'margin-bottom': '13px', - 'margin-top': '13px', - 'padding-inline-start': '40px', - 'padding-left': '40px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1472px' - }, - OPTGROUP: { - 'block-size': '16.5938px', - display: 'block', - 'font-weight': '700', - height: '16.5938px', - 'inline-size': '1000px', - 'perspective-origin': '756px 8.29688px', - 'transform-origin': '756px 8.29688px', - width: '1000px' - }, - OPTION: { - 'block-size': '15.5938px', - display: 'block', - height: '15.5938px', - 'inline-size': '1508px', - 'min-block-size': '15.6px', - 'min-height': '15.6px', - 'padding-block-end': '1px', - 'padding-bottom': '1px', - 'padding-inline-end': '2px', - 'padding-inline-start': '2px', - 'padding-left': '2px', - 'padding-right': '2px', - 'perspective-origin': '756px 8.29688px', - 'transform-origin': '756px 8.29688px', - 'white-space': 'nowrap', - width: '1508px' - }, - OUTPUT: { - 'unicode-bidi': 'isolate' - }, - P: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'margin-block-end': '13px', - 'margin-block-start': '13px', - 'margin-bottom': '13px', - 'margin-top': '13px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - PARAM: { - display: 'none', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - PICTURE: {}, - PRE: { - 'block-size': '0px', - display: 'block', - 'font-family': 'monospace', - 'font-size': '10.5625px', - height: '0px', - 'inline-size': '1000px', - 'margin-block-end': '10.5625px', - 'margin-block-start': '10.5625px', - 'margin-bottom': '10.5625px', - 'margin-top': '10.5625px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - 'white-space': 'pre', - width: '1000px' - }, - PROGRESS: { - appearance: 'auto', - 'block-size': '13px', - 'box-sizing': 'border-box', - display: 'inline-block', - height: '13px', - 'inline-size': '130px', - 'perspective-origin': '65px 6.5px', - 'transform-origin': '65px 6.5px', - 'vertical-align': '-2.6px', - width: '130px' - }, - Q: {}, - RB: {}, - RP: { - display: 'none', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - RT: {}, - RTC: {}, - RUBY: {}, - S: { - 'text-decoration': 'line-through solid rgb(0, 0, 0)', - 'text-decoration-line': 'line-through', - '-webkit-text-decorations-in-effect': 'line-through' - }, - SAMP: { - 'font-family': 'monospace', - 'font-size': '10.5625px' - }, - SECTION: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - SELECT: { - 'align-items': 'center', - appearance: 'auto', - 'background-color': 'rgb(255, 255, 255)', - 'block-size': '19px', - 'border-block-end-color': 'rgb(118, 118, 118)', - 'border-block-end-style': 'solid', - 'border-block-end-width': '1px', - 'border-block-start-color': 'rgb(118, 118, 118)', - 'border-block-start-style': 'solid', - 'border-block-start-width': '1px', - 'border-bottom-color': 'rgb(118, 118, 118)', - 'border-bottom-style': 'solid', - 'border-bottom-width': '1px', - 'border-inline-end-color': 'rgb(118, 118, 118)', - 'border-inline-end-style': 'solid', - 'border-inline-end-width': '1px', - 'border-inline-start-color': 'rgb(118, 118, 118)', - 'border-inline-start-style': 'solid', - 'border-inline-start-width': '1px', - 'border-left-color': 'rgb(118, 118, 118)', - 'border-left-style': 'solid', - 'border-left-width': '1px', - 'border-right-color': 'rgb(118, 118, 118)', - 'border-right-style': 'solid', - 'border-right-width': '1px', - 'border-top-color': 'rgb(118, 118, 118)', - 'border-top-style': 'solid', - 'border-top-width': '1px', - 'box-sizing': 'border-box', - cursor: 'default', - display: 'inline-block', - 'font-family': 'Arial', - 'font-size': '13.3333px', - height: '19px', - 'inline-size': '22px', - 'perspective-origin': '11px 9.5px', - 'transform-origin': '11px 9.5px', - 'white-space': 'pre', - width: '22px' - }, - SMALL: { - 'font-size': '10.8333px' - }, - SOURCE: {}, - SPAN: {}, - STRONG: { - 'font-weight': '700' - }, - SUB: { - 'font-size': '10.8333px', - 'vertical-align': 'sub' - }, - SUMMARY: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - SUP: { - 'font-size': '10.8333px', - 'vertical-align': 'super' - }, - TABLE: { - 'block-size': '0px', - 'border-block-end-color': 'rgb(128, 128, 128)', - 'border-block-start-color': 'rgb(128, 128, 128)', - 'border-bottom-color': 'rgb(128, 128, 128)', - 'border-inline-end-color': 'rgb(128, 128, 128)', - 'border-inline-start-color': 'rgb(128, 128, 128)', - 'border-left-color': 'rgb(128, 128, 128)', - 'border-right-color': 'rgb(128, 128, 128)', - 'border-top-color': 'rgb(128, 128, 128)', - 'box-sizing': 'border-box', - display: 'table', - height: '0px', - 'inline-size': '0px', - width: '0px', - '-webkit-border-horizontal-spacing': '2px', - '-webkit-border-vertical-spacing': '2px' - }, - TBODY: { - 'block-size': '0px', - display: 'table-row-group', - height: '0px', - 'inline-size': '0px', - 'vertical-align': 'middle', - width: '0px' - }, - TD: { - 'block-size': '0px', - display: 'table-cell', - height: '0px', - 'inline-size': '0px', - width: '0px' - }, - TFOOT: { - 'block-size': '0px', - display: 'table-footer-group', - height: '0px', - 'inline-size': '0px', - 'vertical-align': 'middle', - width: '0px' - }, - TH: { - 'block-size': '0px', - display: 'table-cell', - 'font-weight': '700', - height: '0px', - 'inline-size': '0px', - 'text-align': 'center', - width: '0px' - }, - THEAD: { - 'block-size': '0px', - display: 'table-header-group', - height: '0px', - 'inline-size': '0px', - 'vertical-align': 'middle', - width: '0px' - }, - TIME: {}, - TITLE: { - display: 'none', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - TR: { - 'block-size': '0px', - display: 'table-row', - height: '0px', - 'inline-size': '0px', - width: '0px' - }, - TRACK: {}, - U: { - 'text-decoration': 'underline solid rgb(0, 0, 0)', - 'text-decoration-line': 'underline', - '-webkit-text-decorations-in-effect': 'underline' - }, - UL: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1472px', - 'margin-block-end': '13px', - 'margin-block-start': '13px', - 'margin-bottom': '13px', - 'margin-top': '13px', - 'padding-inline-start': '40px', - 'padding-left': '40px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1472px' - }, - VAR: { - 'font-style': 'italic' - }, - VIDEO: { - 'block-size': '150px', - height: '150px', - 'inline-size': '300px', - 'object-fit': 'contain', - 'perspective-origin': '150px 75px', - 'transform-origin': '150px 75px', - width: '300px' - }, - WBR: {} -}; diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyParser.ts deleted file mode 100644 index 261595ae3..000000000 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyParser.ts +++ /dev/null @@ -1,227 +0,0 @@ -import ICSSStyleDeclarationProperty from '../ICSSStyleDeclarationProperty'; -import CSSStyleDeclarationPropertyValidator from './CSSStyleDeclarationPropertyValidator'; - -/** - * Computed style property parser. - */ -export default class CSSStyleDeclarationPropertyParser { - /** - * Returns style. - * - * @param style Style. - * @param propertyName Property name. - * @returns Element style properties. - */ - public static getPropertyValue( - style: { - [k: string]: ICSSStyleDeclarationProperty; - }, - propertyName: string - ): string { - switch (propertyName) { - case 'margin': - case 'padding': - const padding = style[`${propertyName}-top`]?.value; - if (!padding) { - return ''; - } - for (const property of ['right', 'bottom', 'left']) { - if (style[`${propertyName}-${property}`]?.value !== padding) { - return ''; - } - } - return padding; - case 'border': - case 'border-left': - case 'border-right': - case 'border-top': - case 'border-bottom': - const border = CSSStyleDeclarationBorderUtility.getBorder(styleProperties); - - switch (propertyName) { - case 'border': - return border.left.width && - border.left.width === border.right.width && - border.left.width === border.top.width && - border.left.width === border.bottom.width && - border.left.style && - border.left.style === border.right.style && - border.left.style === border.top.style && - border.left.style === border.bottom.style && - border.left.color === border.right.color && - border.left.color === border.top.color && - border.left.color === border.bottom.color - ? `${border.left.width} ${border.left.style} ${border.left.color}` - : ''; - case 'border-left': - return border.left.width && border.left.style && border.left.color - ? `${border.left.width} ${border.left.style} ${border.left.color}` - : ''; - case 'border-right': - return border.right.width && border.right.style && border.right.color - ? `${border.right.width} ${border.right.style} ${border.right.color}` - : ''; - case 'border-top': - return border.top.width && border.top.style && border.top.color - ? `${border.top.width} ${border.top.style} ${border.top.color}` - : ''; - case 'border-bottom': - return border.bottom.width && border.bottom.style && border.bottom.color - ? `${border.bottom.width} ${border.bottom.style} ${border.bottom.color}` - : ''; - } - } - - return ''; - } - - /** - * Returns valid properties. - * - * @param propertyName Name. - * @param propertyValue Value. - * @returns Properties. - */ - public static getValidProperties( - propertyName: string, - propertyValue: string - ): { [k: string]: ICSSStyleDeclarationProperty } { - const important = propertyValue.endsWith(' !important'); - const name = propertyName; - const value = propertyValue.replace(' !important', ''); - let parts; - - switch (propertyName) { - case 'border': - parts = value.split(/ +/); - - if ( - parts.length < 2 || - !CSSStyleDeclarationPropertyValidator.validateSize(parts[0]) || - !CSSStyleDeclarationPropertyValidator.validateColor(parts[2]) - ) { - return {}; - } - - return { - 'border-top-width': { name, important, value: parts[0] }, - 'border-right-width': { name, important, value: parts[0] }, - 'border-bottom-width': { name, important, value: parts[0] }, - 'border-left-width': { name, important, value: parts[0] }, - 'border-top-style': { name, important, value: parts[1] }, - 'border-right-style': { name, important, value: parts[1] }, - 'border-bottom-style': { name, important, value: parts[1] }, - 'border-left-style': { name, important, value: parts[1] }, - 'border-top-color': { name, important, value: parts[2] }, - 'border-right-color': { name, important, value: parts[2] }, - 'border-bottom-color': { name, important, value: parts[2] }, - 'border-left-color': { name, important, value: parts[2] } - }; - case 'border-left': - case 'border-bottom': - case 'border-right': - case 'border-top': - parts = value.split(/ +/); - - if ( - parts.length < 2 || - !CSSStyleDeclarationPropertyValidator.validateSize(parts[0]) || - !CSSStyleDeclarationPropertyValidator.validateColor(parts[2]) - ) { - return {}; - } - - const borderName = name.split('-')[1]; - - return { - [`border-${borderName}-width`]: { name, important, value: parts[0] }, - [`border-${borderName}-style`]: { name, important, value: parts[1] }, - [`border-${borderName}-color`]: { name, important, value: parts[2] } - }; - case 'border-width': - if (!CSSStyleDeclarationPropertyValidator.validateSize(value)) { - return {}; - } - return { - 'border-top-width': { name, important, value }, - 'border-right-width': { name, important, value }, - 'border-bottom-width': { name, important, value }, - 'border-left-width': { name, important, value } - }; - case 'border-style': - return { - 'border-top-style': { name, important, value }, - 'border-right-style': { name, important, value }, - 'border-bottom-style': { name, important, value }, - 'border-left-style': { name, important, value } - }; - case 'border-color': - if (!CSSStyleDeclarationPropertyValidator.validateColor(value)) { - return {}; - } - return { - 'border-top-color': { name, important, value }, - 'border-right-color': { name, important, value }, - 'border-bottom-color': { name, important, value }, - 'border-left-color': { name, important, value } - }; - case 'border-radius': - parts = value.split(/ +/); - if ( - !value || - !CSSStyleDeclarationPropertyValidator.validateSize(parts[0]) || - (parts[1] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[1])) || - (parts[2] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[2])) || - (parts[3] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[3])) - ) { - return {}; - } - return { - 'border-top-left-radius': { name, important, value: parts[0] || parts[0] }, - 'border-top-right-radius': { name, important, value: parts[1] || parts[0] }, - 'border-bottom-right-radius': { name, important, value: parts[2] || parts[0] }, - 'border-bottom-left-radius': { name, important, value: parts[3] || parts[1] || parts[0] } - }; - case 'padding': - case 'margin': - parts = value.split(/ +/); - if ( - !value || - !CSSStyleDeclarationPropertyValidator.validateSize(parts[0]) || - (parts[1] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[1])) || - (parts[2] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[2])) || - (parts[3] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[3])) - ) { - return {}; - } - return { - [`${name}-top`]: { name, important, value: parts[0] }, - [`${name}-right`]: { name, important, value: parts[1] || parts[0] }, - [`${name}-bottom`]: { name, important, value: parts[2] || parts[0] }, - [`${name}-left`]: { name, important, value: parts[3] || parts[1] || parts[0] } - }; - case 'padding-top': - case 'padding-bottom': - case 'padding-left': - case 'padding-right': - case 'margin-top': - case 'margin-bottom': - case 'margin-left': - case 'margin-right': - case 'border-top-width': - case 'border-bottom-width': - case 'border-left-width': - case 'border-right-width': - if (!CSSStyleDeclarationPropertyValidator.validateSize(value)) { - return {}; - } - return { - [name]: { name, important, value } - }; - } - - return { - [name]: { name, important, value } - }; - } -} diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValidator.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValidator.ts deleted file mode 100644 index 19250b27d..000000000 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValidator.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Computed style property parser. - */ -export default class CSSStyleDeclarationPropertyValidator { - /** - * Validates size. - * - * @param _value Value. - * @returns "true" if valid. - */ - public static validateSize(_value: string): boolean { - return true; - } - - /** - * Validates color. - * - * @param _value Value. - * @returns "true" if valid. - */ - public static validateColor(_value: string): boolean { - return true; - } -} diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStyleParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStyleParser.ts new file mode 100644 index 000000000..3f38c8ded --- /dev/null +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStyleParser.ts @@ -0,0 +1,92 @@ +import IElement from '../../../nodes/element/IElement'; +import ICSSStyleDeclarationProperty from '../ICSSStyleDeclarationProperty'; +import CSSStyleDeclarationPropertyParser from './CSSStyleDeclarationStylePropertyParser'; + +/** + * CSS Style Declaration utility + */ +export default class CSSStyleDeclarationStyleParser { + /** + * 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(';'); + + 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) { + Object.assign( + style, + CSSStyleDeclarationPropertyParser.getValidProperties({ + name: trimmedName, + value: valueWithoutImportant, + important + }) + ); + } + } + } + } + } + + return style; + } + + /** + * 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; + } + + /** + * 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 {}; + } +} diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyParser.ts new file mode 100644 index 000000000..917d3cd85 --- /dev/null +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyParser.ts @@ -0,0 +1,474 @@ +import ICSSStyleDeclarationProperty from '../ICSSStyleDeclarationProperty'; +import CSSStyleDeclarationPropertyValidator from './CSSStyleDeclarationStylePropertyValidator'; + +const URL_REGEXP = /^url\(\s*([^)]*)\s*\)$/; +const BACKGROUND_POSITIONS = ['top', 'center', 'bottom', 'left', 'right', 'inherit']; + +/** + * Computed style property parser. + */ +export default class CSSStyleDeclarationStylePropertyParser { + /** + * Returns property value. + * + * @param style Style. + * @param propertyName Property name. + * @returns Property value. + */ + public static getPropertyValue( + style: { + [k: string]: ICSSStyleDeclarationProperty; + }, + propertyName: string + ): string { + switch (propertyName) { + case 'margin': + if (!style['margin-top']?.value) { + return ''; + } + return `${style['margin-top']?.value} ${style['margin-right']?.value} ${style['margin-bottom']?.value} ${style['margin-left']?.value}` + .replace(/ /g, '') + .trim(); + case 'padding': + if (!style['padding-top']?.value) { + return ''; + } + return `${style['padding-top']?.value} ${style['padding-right']?.value} ${style['padding-bottom']?.value} ${style['padding-left']?.value}` + .replace(/ /g, '') + .trim(); + case 'border': + if ( + !style['border-top-width']?.value || + !style['border-top-style']?.value || + !style['border-top-color']?.value || + style['border-right-width']?.value !== style['border-top-width']?.value || + style['border-right-style']?.value !== style['border-top-style']?.value || + style['border-right-color']?.value !== style['border-top-color']?.value || + style['border-bottom-width']?.value !== style['border-top-width']?.value || + style['border-bottom-style']?.value !== style['border-top-style']?.value || + style['border-bottom-color']?.value !== style['border-top-color']?.value || + style['border-left-width']?.value !== style['border-top-width']?.value || + style['border-left-style']?.value !== style['border-top-style']?.value || + style['border-left-color']?.value !== style['border-top-color']?.value + ) { + return ''; + } + return `${style['border-top-width']?.value} ${style['border-top-style']?.value} ${style['border-top-color']?.value}`; + case 'border-left': + if ( + !style['border-left-width']?.value || + !style['border-left-style']?.value || + !style['border-left-color']?.value + ) { + return ''; + } + return `${style['border-left-width']?.value} ${style['border-left-style']?.value} ${style['border-left-color']?.value}`; + case 'border-right': + if ( + !style['border-right-width']?.value || + !style['border-right-style']?.value || + !style['border-right-color']?.value + ) { + return ''; + } + return `${style['border-right-width']?.value} ${style['border-right-style']?.value} ${style['border-right-color']?.value}`; + case 'border-top': + if ( + !style['border-top-width']?.value || + !style['border-top-style']?.value || + !style['border-top-color']?.value + ) { + return ''; + } + return `${style['border-top-width']?.value} ${style['border-top-style']?.value} ${style['border-top-color']?.value}`; + case 'border-bottom': + if ( + !style['border-bottom-width']?.value || + !style['border-bottom-style']?.value || + !style['border-bottom-color']?.value + ) { + return ''; + } + return `${style['border-bottom-width']?.value} ${style['border-bottom-style']?.value} ${style['border-bottom-color']?.value}`; + case 'background': + if (!style['background-color']?.value && !style['background-image']?.value) { + return ''; + } + return `${style['background-color']?.value} ${style['background-image']?.value} ${style['background-repeat']?.value} ${style['background-attachment']?.value} ${style['background-position']?.value}` + .replace(/ /g, '') + .trim(); + } + + return ''; + } + + /** + * Returns valid properties. + * + * @param property Property. + * @returns Properties. + */ + public static getValidProperties(property: ICSSStyleDeclarationProperty): { + [k: string]: ICSSStyleDeclarationProperty; + } { + const { name, value, important } = property; + let parts; + + switch (name) { + case 'border': + parts = value.split(/ +/); + + if ( + parts.length < 2 || + !CSSStyleDeclarationPropertyValidator.validateSize(parts[0]) || + !CSSStyleDeclarationPropertyValidator.validateBorderStyle(parts[1]) || + !CSSStyleDeclarationPropertyValidator.validateColor(parts[2]) + ) { + return {}; + } + + return { + 'border-top-width': { name, important, value: parts[0] }, + 'border-right-width': { name, important, value: parts[0] }, + 'border-bottom-width': { name, important, value: parts[0] }, + 'border-left-width': { name, important, value: parts[0] }, + 'border-top-style': { name, important, value: parts[1] }, + 'border-right-style': { name, important, value: parts[1] }, + 'border-bottom-style': { name, important, value: parts[1] }, + 'border-left-style': { name, important, value: parts[1] }, + 'border-top-color': { name, important, value: parts[2] }, + 'border-right-color': { name, important, value: parts[2] }, + 'border-bottom-color': { name, important, value: parts[2] }, + 'border-left-color': { name, important, value: parts[2] } + }; + case 'border-left': + case 'border-bottom': + case 'border-right': + case 'border-top': + parts = value.split(/ +/); + + if ( + parts.length < 2 || + !CSSStyleDeclarationPropertyValidator.validateSize(parts[0]) || + !CSSStyleDeclarationPropertyValidator.validateBorderStyle(parts[1]) || + !CSSStyleDeclarationPropertyValidator.validateColor(parts[2]) + ) { + return {}; + } + + const borderName = name.split('-')[1]; + + return { + [`border-${borderName}-width`]: { name, important, value: parts[0] }, + [`border-${borderName}-style`]: { name, important, value: parts[1] }, + [`border-${borderName}-color`]: { name, important, value: parts[2] } + }; + case 'border-width': + if (!CSSStyleDeclarationPropertyValidator.validateSize(value)) { + return {}; + } + return { + 'border-top-width': { name, important, value }, + 'border-right-width': { name, important, value }, + 'border-bottom-width': { name, important, value }, + 'border-left-width': { name, important, value } + }; + case 'border-style': + if (!CSSStyleDeclarationPropertyValidator.validateBorderStyle(value)) { + return {}; + } + return { + 'border-top-style': { name, important, value }, + 'border-right-style': { name, important, value }, + 'border-bottom-style': { name, important, value }, + 'border-left-style': { name, important, value } + }; + case 'border-color': + if (!CSSStyleDeclarationPropertyValidator.validateColor(value)) { + return {}; + } + return { + 'border-top-color': { name, important, value }, + 'border-right-color': { name, important, value }, + 'border-bottom-color': { name, important, value }, + 'border-left-color': { name, important, value } + }; + case 'border-radius': + parts = value.split(/ +/); + if ( + !value || + !CSSStyleDeclarationPropertyValidator.validateSize(parts[0]) || + (parts[1] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[1])) || + (parts[2] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[2])) || + (parts[3] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[3])) + ) { + return {}; + } + return { + 'border-top-left-radius': { name, important, value: parts[0] || '' }, + 'border-top-right-radius': { name, important, value: parts[1] || '' }, + 'border-bottom-right-radius': { name, important, value: parts[2] || '' }, + 'border-bottom-left-radius': { name, important, value: parts[3] || '' } + }; + case 'padding': + case 'margin': + parts = value.split(/ +/); + if ( + !parts[0] || + !CSSStyleDeclarationPropertyValidator.validateSize(parts[0]) || + (parts[1] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[1])) || + (parts[2] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[2])) || + (parts[3] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[3])) + ) { + return {}; + } + return { + [`${name}-top`]: { name, important, value: parts[0] }, + [`${name}-right`]: { name, important, value: parts[1] || '' }, + [`${name}-bottom`]: { name, important, value: parts[2] || '' }, + [`${name}-left`]: { name, important, value: parts[3] || '' } + }; + case 'background': + parts = value.split(/ +/); + // First value can be color or image url + if(!CSSStyleDeclarationPropertyValidator.validateColor(parts[0])) { + parts.unshift(''); + } + const backgroundImage = this.parseURL(parts[1]); + const backgroundPosition = this.parseBackgroundPosition(parts(4)); + if ( + !value || + (parts[0] && !CSSStyleDeclarationPropertyValidator.validateColor(parts[0])) || + (parts[1] && !backgroundImage) || + (parts[2] && !CSSStyleDeclarationPropertyValidator.validateBackgroundRepeat(parts[2])) || + (parts[3] && + !CSSStyleDeclarationPropertyValidator.validateBackgroundAttachment(parts[3])) || + (parts[4] && !backgroundPosition) + ) { + return {}; + } + return { + 'background-color': { name, important, value: parts[0] || '' }, + 'background-image': { name, important, value: backgroundImage }, + 'background-repeat': { name, important, value: parts[2] || '' }, + 'background-attachment': { name, important, value: parts[3] || '' }, + 'background-position': { name, important, value: backgroundPosition }, + }; + case 'padding-top': + case 'padding-bottom': + case 'padding-left': + case 'padding-right': + case 'margin-top': + case 'margin-bottom': + case 'margin-left': + case 'margin-right': + case 'border-top-width': + case 'border-bottom-width': + case 'border-left-width': + case 'border-right-width': + case 'font-size': + if (!CSSStyleDeclarationPropertyValidator.validateSize(value)) { + return {}; + } + return { + [name]: { name, important, value } + }; + case 'border-top-color': + case 'border-bottom-color': + case 'border-left-color': + case 'border-right-color': + if (!CSSStyleDeclarationPropertyValidator.validateColor(value)) { + return {}; + } + return { + [name]: { name, important, value } + }; + case 'border-top-style': + case 'border-bottom-style': + case 'border-left-style': + case 'border-right-style': + if (!CSSStyleDeclarationPropertyValidator.validateBorderStyle(value)) { + return {}; + } + return { + [name]: { name, important, value } + }; + } + + return { + [name]: { name, important, value } + }; + } + + /** + * Returns related property names. + * + * @param propertyName Property name. + * @returns Properties. + */ + public static getRelatedPropertyNames(propertyName: string): string[] { + const name = propertyName; + + switch (name) { + case 'border': + return [ + 'border-top-width', + 'border-right-width', + 'border-bottom-width', + 'border-left-width', + 'border-top-style', + 'border-right-style', + 'border-bottom-style', + 'border-left-style', + 'border-top-color', + 'border-right-color', + 'border-bottom-color', + 'border-left-color' + ]; + case 'border-left': + case 'border-bottom': + case 'border-right': + case 'border-top': + const borderName = name.split('-')[1]; + + return [ + `border-${borderName}-width`, + `border-${borderName}-style`, + `border-${borderName}-color` + ]; + case 'border-width': + return [ + 'border-top-width', + 'border-right-width', + 'border-bottom-width', + 'border-left-width' + ]; + case 'border-style': + return [ + 'border-top-style', + 'border-right-style', + 'border-bottom-style', + 'border-left-style' + ]; + case 'border-color': + return [ + 'border-top-color', + 'border-right-color', + 'border-bottom-color', + 'border-left-color' + ]; + case 'border-radius': + return [ + 'border-top-left-radius', + 'border-top-right-radius', + 'border-bottom-right-radius', + 'border-bottom-left-radius' + ]; + case 'background': + return [ + 'background-color', + 'background-image', + 'background-repeat', + 'background-attachment', + 'background-position' + ] + case 'padding': + case 'margin': + return [`${name}-top`, `${name}-right`, `${name}-bottom`, `${name}-left`]; + } + + return [name]; + } + + /** + * Parses URL. + * + * Based on: + * https://github.com/jsdom/cssstyle/blob/master/lib/parsers.js#L222 + * + * @param value + * @returns New value. + */ + private static parseURL(value: string): string { + if (!value) { + return ''; + } + + if(value === 'none' || value === 'inherit') { + return value; + } + + const result = URL_REGEXP.exec(value); + + if (!result) { + return ''; + } + + let url = result[1]; + + if ((url[0] === '"' || url[0] === "'") && url[0] !== url[url.length - 1]) { + return ''; + } + + if (url[0] === '"' || url[0] === "'") { + url = url.substring(1, url.length - 1); + } + + for (let i = 0; i < url.length; i++) { + switch (url[i]) { + case '(': + case ')': + case ' ': + case '\t': + case '\n': + case "'": + case '"': + return ''; + case '\\': + i++; + break; + } + } + + return 'url(' + url + ')'; + } + + /** + * Parses URL. + * + * Based on: + * https://github.com/jsdom/cssstyle/blob/master/lib/properties/backgroundPosition.js + * + * @param value + * @returns New value. + */ + private static parseBackgroundPosition(value: string): string { + if (!value) { + return ''; + } + const parts = value.split(/\s+/); + if (parts.length > 2 || parts.length < 1) { + return ''; + } + if (parts.length === 1) { + if (CSSStyleDeclarationPropertyValidator.validateSize(parts[0])) { + return value; + } + if (parts[0]) { + if (BACKGROUND_POSITIONS.includes(value.toLowerCase())) { + return value; + } + } + return ''; + } + if ( + CSSStyleDeclarationPropertyValidator.validateSize(parts[0]) && CSSStyleDeclarationPropertyValidator.validateSize(parts[1]) + ) { + return value; + } + if (BACKGROUND_POSITIONS.includes(parts[0].toLowerCase()) && BACKGROUND_POSITIONS.includes(parts[1].toLowerCase())) { + return value; + } + return ''; + } +} diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyValidator.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyValidator.ts new file mode 100644 index 000000000..8f091e1a8 --- /dev/null +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyValidator.ts @@ -0,0 +1,84 @@ +const COLOR_REGEXP = + /^#([0-9a-fA-F]{3,4}){1,2}$|^rgb\(([^)]*)\)$|^rgba\(([^)]*)\)$|^hsla?\(\s*(-?\d+|-?\d*.\d+)\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)%\s*(,\s*(-?\d+|-?\d*.\d+)\s*)?\)/; + +const SIZE_REGEXP = + /^(0|[-+]?[0-9]*\.?[0-9]+(in|cm|em|mm|pt|pc|px|ex|rem|vh|vw|ch))$|^[-+]?[0-9]*\.?[0-9]+%$/; +const BORDER_STYLE = [ + 'none', + 'hidden', + 'dotted', + 'dashed', + 'solid', + 'double', + 'groove', + 'ridge', + 'inset', + 'outset' +]; +const BACKGROUND_REPEAT = [ + 'repeat', + 'repeat-x', + 'repeat-y', + 'no-repeat', + 'inherit' +]; +const BACKGROUND_ATTACHMENT = [ + 'scroll', + 'fixed', + 'inherit' +]; + +/** + * Computed style property parser. + */ +export default class CSSStyleDeclarationStylePropertyValidator { + /** + * Validates size. + * + * @param value Value. + * @returns "true" if valid. + */ + public static validateSize(value: string): boolean { + return SIZE_REGEXP.test(value); + } + + /** + * Validates color. + * + * @param value Value. + * @returns "true" if valid. + */ + public static validateColor(value: string): boolean { + return COLOR_REGEXP.test(value); + } + + /** + * Validates border style. + * + * @param value Value. + * @returns "true" if valid. + */ + public static validateBorderStyle(value: string): boolean { + return BORDER_STYLE.includes(value.toLowerCase()); + } + + /** + * Validates background repeat. + * + * @param value Value. + * @returns "true" if valid. + */ + public static validateBackgroundRepeat(value: string): boolean { + return BACKGROUND_REPEAT.includes(value.toLowerCase()); + } + + /** + * Validates background attachment. + * + * @param value Value. + * @returns "true" if valid. + */ + public static validateBackgroundAttachment(value: string): boolean { + return BACKGROUND_ATTACHMENT.includes(value.toLowerCase()); + } +} diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationUtility.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationUtility.ts deleted file mode 100644 index 55108136e..000000000 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationUtility.ts +++ /dev/null @@ -1,153 +0,0 @@ -import IElement from '../../../nodes/element/IElement'; -import ICSSStyleDeclarationProperty from '../ICSSStyleDeclarationProperty'; -import CSSStyleDeclarationPropertyParser from './CSSStyleDeclarationPropertyParser'; - -/** - * CSS Style Declaration utility - */ -export default class CSSStyleDeclarationUtility { - /** - * Returns a style from a string. - * - * @param styleString Style string (e.g. "border: 2px solid red; font-size: 12px;"). - * @returns Style. - */ - public static stringToStyle(styleString: string): { - [k: string]: ICSSStyleDeclarationProperty; - } { - const style = {}; - const parts = styleString.split(';'); - - 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) { - Object.assign( - style, - CSSStyleDeclarationPropertyParser.parseProperty(trimmedName, trimmedValue) - ); - } - } - } - } - - return style; - } - - /** - * Returns a style string. - * - * @param style Style. - * @returns Styles as string. - */ - public static styleToString(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; - } - - /** - * Returns element style properties. - * - * @param element Element. - * @param [computed] Computed. - * @returns Element style properties. - */ - public static getElementStyle( - element: IElement, - computed = false - ): { - [k: string]: ICSSStyleDeclarationProperty; - } { - if (computed) { - // TODO: Add logic for style sheets - } - if (element['_attributes']['style'] && element['_attributes']['style'].value) { - return this.stringToStyle(element['_attributes']['style'].value); - } - - return {}; - } - - /** - * Returns style. - * - * @param style Style. - * @param propertyName Property name. - * @returns Element style properties. - */ - public static getPropertyValue( - style: { - [k: string]: ICSSStyleDeclarationProperty; - }, - propertyName: string - ): string { - switch (propertyName) { - case 'margin': - case 'padding': - const padding = style[`${propertyName}-top`]?.value; - if (!padding) { - return ''; - } - for (const property of ['right', 'bottom', 'left']) { - if (style[`${propertyName}-${property}`]?.value !== padding) { - return ''; - } - } - return padding; - case 'border': - case 'border-left': - case 'border-right': - case 'border-top': - case 'border-bottom': - const border = CSSStyleDeclarationBorderUtility.getBorder(styleProperties); - - switch (propertyName) { - case 'border': - return border.left.width && - border.left.width === border.right.width && - border.left.width === border.top.width && - border.left.width === border.bottom.width && - border.left.style && - border.left.style === border.right.style && - border.left.style === border.top.style && - border.left.style === border.bottom.style && - border.left.color === border.right.color && - border.left.color === border.top.color && - border.left.color === border.bottom.color - ? `${border.left.width} ${border.left.style} ${border.left.color}` - : ''; - case 'border-left': - return border.left.width && border.left.style && border.left.color - ? `${border.left.width} ${border.left.style} ${border.left.color}` - : ''; - case 'border-right': - return border.right.width && border.right.style && border.right.color - ? `${border.right.width} ${border.right.style} ${border.right.color}` - : ''; - case 'border-top': - return border.top.width && border.top.style && border.top.color - ? `${border.top.width} ${border.top.style} ${border.top.color}` - : ''; - case 'border-bottom': - return border.bottom.width && border.bottom.style && border.bottom.color - ? `${border.bottom.width} ${border.bottom.style} ${border.bottom.color}` - : ''; - } - } - - return ''; - } -} diff --git a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts index e597d344e..fc8e0d223 100644 --- a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts +++ b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts @@ -3,7 +3,7 @@ import Window from '../../../src/window/Window'; import IWindow from '../../../src/window/IWindow'; import IDocument from '../../../src/nodes/document/IDocument'; import IElement from '../../../src/nodes/element/IElement'; -import CSSStyleDeclarationDefaultValues from '../../../src/css/computed-style/config/CSSStyleDeclarationDefaultValues'; +import CSSStyleDeclarationDefaultValues from './data/CSSStyleDeclarationDefaultValues'; function KEBAB_TO_CAMEL_CASE(text: string): string { const parts = text.split('-'); diff --git a/packages/happy-dom/src/css/computed-style/config/CSSStyleDeclarationDefaultValues.ts b/packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationDefaultValues.ts similarity index 100% rename from packages/happy-dom/src/css/computed-style/config/CSSStyleDeclarationDefaultValues.ts rename to packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationDefaultValues.ts diff --git a/packages/happy-dom/src/css/computed-style/config/CSSStyleDeclarationNodeDefaultValues.ts b/packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationNodeDefaultValues.ts similarity index 100% rename from packages/happy-dom/src/css/computed-style/config/CSSStyleDeclarationNodeDefaultValues.ts rename to packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationNodeDefaultValues.ts From d6139564122c0c1986f642752626d6ac873e80da Mon Sep 17 00:00:00 2001 From: David Ortner Date: Wed, 10 Aug 2022 00:22:20 +0200 Subject: [PATCH 13/84] #344@trivial: Continues on CSSStyleDeclaration. --- .../CSSStyleDeclarationStylePropertyParser.ts | 247 +++++++++--------- ...SStyleDeclarationStylePropertyValidator.ts | 179 ++++++++++++- 2 files changed, 295 insertions(+), 131 deletions(-) diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyParser.ts index 917d3cd85..8b8eaa044 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyParser.ts @@ -1,9 +1,6 @@ import ICSSStyleDeclarationProperty from '../ICSSStyleDeclarationProperty'; import CSSStyleDeclarationPropertyValidator from './CSSStyleDeclarationStylePropertyValidator'; -const URL_REGEXP = /^url\(\s*([^)]*)\s*\)$/; -const BACKGROUND_POSITIONS = ['top', 'center', 'bottom', 'left', 'right', 'inherit']; - /** * Computed style property parser. */ @@ -53,7 +50,7 @@ export default class CSSStyleDeclarationStylePropertyParser { ) { return ''; } - return `${style['border-top-width']?.value} ${style['border-top-style']?.value} ${style['border-top-color']?.value}`; + return `${style['border-top-width'].value} ${style['border-top-style'].value} ${style['border-top-color'].value}`; case 'border-left': if ( !style['border-left-width']?.value || @@ -62,7 +59,7 @@ export default class CSSStyleDeclarationStylePropertyParser { ) { return ''; } - return `${style['border-left-width']?.value} ${style['border-left-style']?.value} ${style['border-left-color']?.value}`; + return `${style['border-left-width'].value} ${style['border-left-style'].value} ${style['border-left-color'].value}`; case 'border-right': if ( !style['border-right-width']?.value || @@ -71,7 +68,7 @@ export default class CSSStyleDeclarationStylePropertyParser { ) { return ''; } - return `${style['border-right-width']?.value} ${style['border-right-style']?.value} ${style['border-right-color']?.value}`; + return `${style['border-right-width'].value} ${style['border-right-style'].value} ${style['border-right-color'].value}`; case 'border-top': if ( !style['border-top-width']?.value || @@ -80,7 +77,7 @@ export default class CSSStyleDeclarationStylePropertyParser { ) { return ''; } - return `${style['border-top-width']?.value} ${style['border-top-style']?.value} ${style['border-top-color']?.value}`; + return `${style['border-top-width'].value} ${style['border-top-style'].value} ${style['border-top-color'].value}`; case 'border-bottom': if ( !style['border-bottom-width']?.value || @@ -89,17 +86,26 @@ export default class CSSStyleDeclarationStylePropertyParser { ) { return ''; } - return `${style['border-bottom-width']?.value} ${style['border-bottom-style']?.value} ${style['border-bottom-color']?.value}`; - case 'background': + return `${style['border-bottom-width'].value} ${style['border-bottom-style'].value} ${style['border-bottom-color'].value}`; + case 'background': if (!style['background-color']?.value && !style['background-image']?.value) { return ''; } return `${style['background-color']?.value} ${style['background-image']?.value} ${style['background-repeat']?.value} ${style['background-attachment']?.value} ${style['background-position']?.value}` .replace(/ /g, '') .trim(); + case 'flex': + if ( + !style['flex-grow']?.value || + !style['flex-shrink']?.value || + !style['flex-basis']?.value + ) { + return ''; + } + return `${style['flex-grow'].value} ${style['flex-shrink'].value} ${style['flex-basis'].value}`; } - return ''; + return style[propertyName]?.value || ''; } /** @@ -119,7 +125,6 @@ export default class CSSStyleDeclarationStylePropertyParser { parts = value.split(/ +/); if ( - parts.length < 2 || !CSSStyleDeclarationPropertyValidator.validateSize(parts[0]) || !CSSStyleDeclarationPropertyValidator.validateBorderStyle(parts[1]) || !CSSStyleDeclarationPropertyValidator.validateColor(parts[2]) @@ -148,7 +153,6 @@ export default class CSSStyleDeclarationStylePropertyParser { parts = value.split(/ +/); if ( - parts.length < 2 || !CSSStyleDeclarationPropertyValidator.validateSize(parts[0]) || !CSSStyleDeclarationPropertyValidator.validateBorderStyle(parts[1]) || !CSSStyleDeclarationPropertyValidator.validateColor(parts[2]) @@ -196,7 +200,6 @@ export default class CSSStyleDeclarationStylePropertyParser { case 'border-radius': parts = value.split(/ +/); if ( - !value || !CSSStyleDeclarationPropertyValidator.validateSize(parts[0]) || (parts[1] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[1])) || (parts[2] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[2])) || @@ -210,6 +213,98 @@ export default class CSSStyleDeclarationStylePropertyParser { 'border-bottom-right-radius': { name, important, value: parts[2] || '' }, 'border-bottom-left-radius': { name, important, value: parts[3] || '' } }; + case 'border-collapse': + if (!value || !CSSStyleDeclarationPropertyValidator.validateBorderCollapse(value)) { + return {}; + } + return { + [name]: { name, important, value } + }; + case 'clear': + if (!value || !CSSStyleDeclarationPropertyValidator.validateClear(value)) { + return {}; + } + return { + [name]: { name, important, value } + }; + case 'clip': + if (!value || !CSSStyleDeclarationPropertyValidator.validateClip(value)) { + return {}; + } + return { + [name]: { name, important, value } + }; + case 'css-float': + case 'float': + if (!value || !CSSStyleDeclarationPropertyValidator.validateFloat(value)) { + return {}; + } + return { + [name]: { name, important, value } + }; + case 'flex': + const lowerValue = value.trim().toLowerCase(); + switch (lowerValue) { + case 'none': + return { + 'flex-grow': { name, important, value: '0' }, + 'flex-shrink': { name, important, value: '0' }, + 'flex-basis': { name, important, value: 'auto' } + }; + case 'auto': + return { + 'flex-grow': { name, important, value: '1' }, + 'flex-shrink': { name, important, value: '1' }, + 'flex-basis': { name, important, value: 'auto' } + }; + case 'initial': + return { + 'flex-grow': { name, important, value: '0' }, + 'flex-shrink': { name, important, value: '1' }, + 'flex-basis': { name, important, value: 'auto' } + }; + case 'inherit': + return { + 'flex-grow': { name, important, value: 'inherit' }, + 'flex-shrink': { name, important, value: 'inherit' }, + 'flex-basis': { name, important, value: 'inherit' } + }; + } + + parts = value.split(/ +/); + if ( + !CSSStyleDeclarationPropertyValidator.validateInteger(parts[0]) || + !CSSStyleDeclarationPropertyValidator.validateInteger(parts[1]) || + (parts[2] !== 'auto' && + parts[2] !== 'inherit' && + !CSSStyleDeclarationPropertyValidator.validateSize(parts[2])) + ) { + return {}; + } + return { + 'flex-grow': { name, important, value: parts[0] }, + 'flex-shrink': { name, important, value: parts[1] }, + 'flex-basis': { name, important, value: parts[2] } + }; + case 'flex-shrink': + case 'flex-grow': + if (!value || !CSSStyleDeclarationPropertyValidator.validateInteger(value)) { + return {}; + } + return { + [name]: { name, important, value } + }; + case 'flex-basis': + if ( + value !== 'auto' && + value !== 'inherit' && + !CSSStyleDeclarationPropertyValidator.validateSize(value) + ) { + return {}; + } + return { + [name]: { name, important, value } + }; case 'padding': case 'margin': parts = value.split(/ +/); @@ -230,30 +325,32 @@ export default class CSSStyleDeclarationStylePropertyParser { }; case 'background': parts = value.split(/ +/); - // First value can be color or image url - if(!CSSStyleDeclarationPropertyValidator.validateColor(parts[0])) { - parts.unshift(''); - } - const backgroundImage = this.parseURL(parts[1]); - const backgroundPosition = this.parseBackgroundPosition(parts(4)); + // First value can be color or image url + if (!CSSStyleDeclarationPropertyValidator.validateColor(parts[0])) { + parts.unshift(''); + } if ( - !value || + (!parts[0] && !parts[1]) || (parts[0] && !CSSStyleDeclarationPropertyValidator.validateColor(parts[0])) || - (parts[1] && !backgroundImage) || + (parts[1] && !CSSStyleDeclarationPropertyValidator.validateURL(parts[1])) || (parts[2] && !CSSStyleDeclarationPropertyValidator.validateBackgroundRepeat(parts[2])) || (parts[3] && !CSSStyleDeclarationPropertyValidator.validateBackgroundAttachment(parts[3])) || - (parts[4] && !backgroundPosition) + (parts[4] && !CSSStyleDeclarationPropertyValidator.validateBackgroundPosition(parts[4])) ) { return {}; } return { 'background-color': { name, important, value: parts[0] || '' }, - 'background-image': { name, important, value: backgroundImage }, + 'background-image': { name, important, value: parts[1] || '' }, 'background-repeat': { name, important, value: parts[2] || '' }, 'background-attachment': { name, important, value: parts[3] || '' }, - 'background-position': { name, important, value: backgroundPosition }, + 'background-position': { name, important, value: parts[4] || '' } }; + case 'top': + case 'right': + case 'bottom': + case 'left': case 'padding-top': case 'padding-bottom': case 'padding-left': @@ -273,6 +370,8 @@ export default class CSSStyleDeclarationStylePropertyParser { return { [name]: { name, important, value } }; + case 'color': + case 'flood-color': case 'border-top-color': case 'border-bottom-color': case 'border-left-color': @@ -364,14 +463,16 @@ export default class CSSStyleDeclarationStylePropertyParser { 'border-bottom-right-radius', 'border-bottom-left-radius' ]; - case 'background': - return [ + case 'background': + return [ 'background-color', 'background-image', 'background-repeat', 'background-attachment', 'background-position' - ] + ]; + case 'flex': + return ['flex-grow', 'flex-shrink', 'flex-basis']; case 'padding': case 'margin': return [`${name}-top`, `${name}-right`, `${name}-bottom`, `${name}-left`]; @@ -379,96 +480,4 @@ export default class CSSStyleDeclarationStylePropertyParser { return [name]; } - - /** - * Parses URL. - * - * Based on: - * https://github.com/jsdom/cssstyle/blob/master/lib/parsers.js#L222 - * - * @param value - * @returns New value. - */ - private static parseURL(value: string): string { - if (!value) { - return ''; - } - - if(value === 'none' || value === 'inherit') { - return value; - } - - const result = URL_REGEXP.exec(value); - - if (!result) { - return ''; - } - - let url = result[1]; - - if ((url[0] === '"' || url[0] === "'") && url[0] !== url[url.length - 1]) { - return ''; - } - - if (url[0] === '"' || url[0] === "'") { - url = url.substring(1, url.length - 1); - } - - for (let i = 0; i < url.length; i++) { - switch (url[i]) { - case '(': - case ')': - case ' ': - case '\t': - case '\n': - case "'": - case '"': - return ''; - case '\\': - i++; - break; - } - } - - return 'url(' + url + ')'; - } - - /** - * Parses URL. - * - * Based on: - * https://github.com/jsdom/cssstyle/blob/master/lib/properties/backgroundPosition.js - * - * @param value - * @returns New value. - */ - private static parseBackgroundPosition(value: string): string { - if (!value) { - return ''; - } - const parts = value.split(/\s+/); - if (parts.length > 2 || parts.length < 1) { - return ''; - } - if (parts.length === 1) { - if (CSSStyleDeclarationPropertyValidator.validateSize(parts[0])) { - return value; - } - if (parts[0]) { - if (BACKGROUND_POSITIONS.includes(value.toLowerCase())) { - return value; - } - } - return ''; - } - if ( - CSSStyleDeclarationPropertyValidator.validateSize(parts[0]) && CSSStyleDeclarationPropertyValidator.validateSize(parts[1]) - ) { - return value; - } - if (BACKGROUND_POSITIONS.includes(parts[0].toLowerCase()) && BACKGROUND_POSITIONS.includes(parts[1].toLowerCase())) { - return value; - } - return ''; - } } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyValidator.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyValidator.ts index 8f091e1a8..b09b73fa6 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyValidator.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyValidator.ts @@ -3,6 +3,8 @@ const COLOR_REGEXP = const SIZE_REGEXP = /^(0|[-+]?[0-9]*\.?[0-9]+(in|cm|em|mm|pt|pc|px|ex|rem|vh|vw|ch))$|^[-+]?[0-9]*\.?[0-9]+%$/; +const URL_REGEXP = /^url\(\s*([^)]*)\s*\)$/; +const RECT_REGEXP = /^rect\((.*)\)$/i; const BORDER_STYLE = [ 'none', 'hidden', @@ -15,18 +17,12 @@ const BORDER_STYLE = [ 'inset', 'outset' ]; -const BACKGROUND_REPEAT = [ - 'repeat', - 'repeat-x', - 'repeat-y', - 'no-repeat', - 'inherit' -]; -const BACKGROUND_ATTACHMENT = [ - 'scroll', - 'fixed', - 'inherit' -]; +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 BACKGROUND_POSITIONS = ['top', 'center', 'bottom', 'left', 'right', 'inherit']; +const CLEAR = ['none', 'left', 'right', 'both', 'inherit']; +const FLOAT = ['none', 'left', 'right', 'inherit']; /** * Computed style property parser. @@ -42,6 +38,16 @@ export default class CSSStyleDeclarationStylePropertyValidator { return SIZE_REGEXP.test(value); } + /** + * Validates integer. + * + * @param value Value. + * @returns "true" if valid. + */ + public static validateInteger(value: string): boolean { + return !isNaN(parseInt(value)); + } + /** * Validates color. * @@ -52,6 +58,55 @@ export default class CSSStyleDeclarationStylePropertyValidator { return COLOR_REGEXP.test(value); } + /** + * Validates URL. + * + * Based on: + * https://github.com/jsdom/cssstyle/blob/master/lib/parsers.js#L222 + * + * @param value Value. + * @returns "true" if valid. + */ + public static validateURL(value: string): boolean { + if (!value || value === 'none' || value === 'inherit') { + return false; + } + + const result = URL_REGEXP.exec(value); + + if (!result) { + return false; + } + + let url = result[1]; + + if ((url[0] === '"' || url[0] === "'") && url[0] !== url[url.length - 1]) { + return false; + } + + if (url[0] === '"' || url[0] === "'") { + url = url.substring(1, url.length - 1); + } + + for (let i = 0; i < url.length; i++) { + switch (url[i]) { + case '(': + case ')': + case ' ': + case '\t': + case '\n': + case "'": + case '"': + return false; + case '\\': + i++; + break; + } + } + + return true; + } + /** * Validates border style. * @@ -62,6 +117,16 @@ export default class CSSStyleDeclarationStylePropertyValidator { return BORDER_STYLE.includes(value.toLowerCase()); } + /** + * Validates border collapse. + * + * @param value Value. + * @returns "true" if valid. + */ + public static validateBorderCollapse(value: string): boolean { + return BORDER_COLLAPSE.includes(value.toLowerCase()); + } + /** * Validates background repeat. * @@ -81,4 +146,94 @@ export default class CSSStyleDeclarationStylePropertyValidator { public static validateBackgroundAttachment(value: string): boolean { return BACKGROUND_ATTACHMENT.includes(value.toLowerCase()); } + + /** + * Validates URL. + * + * Based on: + * https://github.com/jsdom/cssstyle/blob/master/lib/properties/backgroundPosition.js + * + * @param value Value. + * @returns "true" if valid. + */ + public static validateBackgroundPosition(value: string): boolean { + if (!value) { + return false; + } + const parts = value.split(/\s+/); + if (parts.length > 2 || parts.length < 1) { + return false; + } + if (parts.length === 1) { + if (this.validateSize(parts[0])) { + return true; + } + if (parts[0]) { + if (BACKGROUND_POSITIONS.includes(value.toLowerCase())) { + return true; + } + } + return false; + } + if (this.validateSize(parts[0]) && this.validateSize(parts[1])) { + return true; + } + if ( + BACKGROUND_POSITIONS.includes(parts[0].toLowerCase()) && + BACKGROUND_POSITIONS.includes(parts[1].toLowerCase()) + ) { + return true; + } + return false; + } + + /** + * Validates clear. + * + * @param value Value. + * @returns "true" if valid. + */ + public static validateClear(value: string): boolean { + return CLEAR.includes(value.toLowerCase()); + } + + /** + * Validates clip + * + * Based on: + * https://github.com/jsdom/cssstyle/blob/master/lib/properties/clip.js + * + * @param value Value. + * @returns "true" if valid. + */ + public static validateClip(value: string): boolean { + const lowerValue = value.toLowerCase(); + if (lowerValue === 'auto' || lowerValue === 'initial' || lowerValue === 'inherit') { + return true; + } + const matches = lowerValue.match(RECT_REGEXP); + if (!matches) { + return false; + } + const parts = matches[1].split(/\s*,\s*/); + if (parts.length !== 4) { + return false; + } + for (const part of parts) { + if (!this.validateSize(part)) { + return false; + } + } + return true; + } + + /** + * Validates float. + * + * @param value Value. + * @returns "true" if valid. + */ + public static validateFloat(value: string): boolean { + return FLOAT.includes(value.toLowerCase()); + } } From 496c47cf5a416f3c41ce4f3adec61b27d04c0d39 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Thu, 11 Aug 2022 01:28:13 +0200 Subject: [PATCH 14/84] #344@trivial: Continues on CSSStyleDeclaration. --- .../AbstractCSSStyleDeclaration.ts | 43 +- .../CSSStyleDeclarationPropertyReader.ts | 108 ++++ .../CSSStyleDeclarationPropertyWriter.ts | 415 +++++++++++++++ ...eDeclarationPropertyWriterPropertyNames.ts | 57 +++ .../CSSStyleDeclarationStylePropertyParser.ts | 483 ------------------ ...SStyleDeclarationStylePropertyValidator.ts | 239 --------- ...r.ts => CSSStyleDeclarationStyleString.ts} | 60 ++- .../CSSStyleDeclarationValueParser.ts | 420 +++++++++++++++ 8 files changed, 1051 insertions(+), 774 deletions(-) create mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyReader.ts create mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyWriter.ts create mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyWriterPropertyNames.ts delete mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyParser.ts delete mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyValidator.ts rename packages/happy-dom/src/css/declaration/utilities/{CSSStyleDeclarationStyleParser.ts => CSSStyleDeclarationStyleString.ts} (84%) create mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts diff --git a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts index 1d483b184..c75774fd6 100644 --- a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts +++ b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts @@ -3,9 +3,10 @@ import Attr from '../../nodes/attr/Attr'; import CSSRule from '../CSSRule'; import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum'; import DOMException from '../../exception/DOMException'; -import CSSStyleDeclarationStyleParser from './utilities/CSSStyleDeclarationStyleParser'; +import CSSStyleDeclarationStyleString from './utilities/CSSStyleDeclarationStyleString'; +import CSSStyleDeclarationPropertyWriter from './utilities/CSSStyleDeclarationPropertyWriter'; import ICSSStyleDeclarationProperty from './ICSSStyleDeclarationProperty'; -import CSSStyleDeclarationStylePropertyParser from './utilities/CSSStyleDeclarationStylePropertyParser'; +import CSSStyleDeclarationPropertyReader from './utilities/CSSStyleDeclarationPropertyReader'; /** * CSS Style Declaration. @@ -36,7 +37,7 @@ export default abstract class AbstractCSSStyleDeclaration { public get length(): number { if (this._ownerElement) { return Object.keys( - CSSStyleDeclarationStyleParser.getElementStyleProperties(this._ownerElement, this._computed) + CSSStyleDeclarationStyleString.getElementStyleProperties(this._ownerElement, this._computed) ).length; } @@ -54,12 +55,12 @@ export default abstract class AbstractCSSStyleDeclaration { return ''; } - return CSSStyleDeclarationStyleParser.getStyleString( - CSSStyleDeclarationStyleParser.getElementStyleProperties(this._ownerElement, this._computed) + return CSSStyleDeclarationStyleString.getStyleString( + CSSStyleDeclarationStyleString.getElementStyleProperties(this._ownerElement, this._computed) ); } - return CSSStyleDeclarationStyleParser.getStyleString(this._styles); + return CSSStyleDeclarationStyleString.getStyleString(this._styles); } /** @@ -76,8 +77,8 @@ export default abstract class AbstractCSSStyleDeclaration { } if (this._ownerElement) { - const parsed = CSSStyleDeclarationStyleParser.getStyleString( - CSSStyleDeclarationStyleParser.getStyleProperties(cssText) + const parsed = CSSStyleDeclarationStyleString.getStyleString( + CSSStyleDeclarationStyleString.getStyleProperties(cssText) ); if (!parsed) { delete this._ownerElement['_attributes']['style']; @@ -91,7 +92,7 @@ export default abstract class AbstractCSSStyleDeclaration { this._ownerElement['_attributes']['style'].value = parsed; } } else { - this._styles = CSSStyleDeclarationStyleParser.getStyleProperties(cssText); + this._styles = CSSStyleDeclarationStyleString.getStyleProperties(cssText); } } @@ -105,7 +106,7 @@ export default abstract class AbstractCSSStyleDeclaration { if (this._ownerElement) { return ( Object.keys( - CSSStyleDeclarationStyleParser.getElementStyleProperties( + CSSStyleDeclarationStyleString.getElementStyleProperties( this._ownerElement, this._computed ) @@ -147,14 +148,14 @@ export default abstract class AbstractCSSStyleDeclaration { this._ownerElement['_attributes']['style'].name = 'style'; } - const elementStyleProperties = CSSStyleDeclarationStyleParser.getElementStyleProperties( + const elementStyleProperties = CSSStyleDeclarationStyleString.getElementStyleProperties( this._ownerElement, this._computed ); Object.assign( elementStyleProperties, - CSSStyleDeclarationStylePropertyParser.getValidProperties({ + CSSStyleDeclarationPropertyWriter.getRelatedProperties({ name: propertyName, value, important: !!priority @@ -162,11 +163,11 @@ export default abstract class AbstractCSSStyleDeclaration { ); this._ownerElement['_attributes']['style'].value = - CSSStyleDeclarationStyleParser.getStyleString(elementStyleProperties); + CSSStyleDeclarationStyleString.getStyleString(elementStyleProperties); } else { Object.assign( this._styles, - CSSStyleDeclarationStylePropertyParser.getValidProperties({ + CSSStyleDeclarationPropertyWriter.getRelatedProperties({ name: propertyName, value, important: !!priority @@ -192,18 +193,18 @@ export default abstract class AbstractCSSStyleDeclaration { if (this._ownerElement) { if (this._ownerElement['_attributes']['style']) { - const elementStyleProperties = CSSStyleDeclarationStyleParser.getElementStyleProperties( + const elementStyleProperties = CSSStyleDeclarationStyleString.getElementStyleProperties( this._ownerElement, this._computed ); const propertiesToRemove = - CSSStyleDeclarationStylePropertyParser.getRelatedPropertyNames(propertyName); + CSSStyleDeclarationPropertyWriter.getRelatedPropertyNames(propertyName); for (const property of Object.keys(propertiesToRemove)) { delete elementStyleProperties[property]; } - const styleString = CSSStyleDeclarationStyleParser.getStyleString(elementStyleProperties); + const styleString = CSSStyleDeclarationStyleString.getStyleString(elementStyleProperties); if (styleString) { this._ownerElement['_attributes']['style'].value = styleString; @@ -213,7 +214,7 @@ export default abstract class AbstractCSSStyleDeclaration { } } else { const propertiesToRemove = - CSSStyleDeclarationStylePropertyParser.getRelatedPropertyNames(propertyName); + CSSStyleDeclarationPropertyWriter.getRelatedPropertyNames(propertyName); for (const property of Object.keys(propertiesToRemove)) { delete this._styles[property]; @@ -229,15 +230,15 @@ export default abstract class AbstractCSSStyleDeclaration { */ public getPropertyValue(propertyName: string): string { if (this._ownerElement) { - const elementStyleProperties = CSSStyleDeclarationStyleParser.getElementStyleProperties( + const elementStyleProperties = CSSStyleDeclarationStyleString.getElementStyleProperties( this._ownerElement, this._computed ); - return CSSStyleDeclarationStylePropertyParser.getPropertyValue( + return CSSStyleDeclarationPropertyReader.getPropertyValue( elementStyleProperties, propertyName ); } - return CSSStyleDeclarationStylePropertyParser.getPropertyValue(this._styles, propertyName); + return CSSStyleDeclarationPropertyReader.getPropertyValue(this._styles, propertyName); } } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyReader.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyReader.ts new file mode 100644 index 000000000..82a40539b --- /dev/null +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyReader.ts @@ -0,0 +1,108 @@ +import ICSSStyleDeclarationProperty from '../ICSSStyleDeclarationProperty'; +/** + * Computed style property parser. + */ +export default class CSSStyleDeclarationPropertyReader { + /** + * Returns property value. + * + * @param style Style. + * @param propertyName Property name. + * @returns Property value. + */ + public static getValue( + style: { + [k: string]: ICSSStyleDeclarationProperty; + }, + propertyName: string + ): string { + switch (propertyName) { + case 'margin': + if (!style['margin-top']?.value) { + return ''; + } + return `${style['margin-top']?.value} ${style['margin-right']?.value} ${style['margin-bottom']?.value} ${style['margin-left']?.value}` + .replace(/ /g, '') + .trim(); + case 'padding': + if (!style['padding-top']?.value) { + return ''; + } + return `${style['padding-top']?.value} ${style['padding-right']?.value} ${style['padding-bottom']?.value} ${style['padding-left']?.value}` + .replace(/ /g, '') + .trim(); + case 'border': + if ( + !style['border-top-width']?.value || + !style['border-top-style']?.value || + !style['border-top-color']?.value || + style['border-right-width']?.value !== style['border-top-width']?.value || + style['border-right-style']?.value !== style['border-top-style']?.value || + style['border-right-color']?.value !== style['border-top-color']?.value || + style['border-bottom-width']?.value !== style['border-top-width']?.value || + style['border-bottom-style']?.value !== style['border-top-style']?.value || + style['border-bottom-color']?.value !== style['border-top-color']?.value || + style['border-left-width']?.value !== style['border-top-width']?.value || + style['border-left-style']?.value !== style['border-top-style']?.value || + style['border-left-color']?.value !== style['border-top-color']?.value + ) { + return ''; + } + return `${style['border-top-width'].value} ${style['border-top-style'].value} ${style['border-top-color'].value}`; + case 'border-left': + if ( + !style['border-left-width']?.value || + !style['border-left-style']?.value || + !style['border-left-color']?.value + ) { + return ''; + } + return `${style['border-left-width'].value} ${style['border-left-style'].value} ${style['border-left-color'].value}`; + case 'border-right': + if ( + !style['border-right-width']?.value || + !style['border-right-style']?.value || + !style['border-right-color']?.value + ) { + return ''; + } + return `${style['border-right-width'].value} ${style['border-right-style'].value} ${style['border-right-color'].value}`; + case 'border-top': + if ( + !style['border-top-width']?.value || + !style['border-top-style']?.value || + !style['border-top-color']?.value + ) { + return ''; + } + return `${style['border-top-width'].value} ${style['border-top-style'].value} ${style['border-top-color'].value}`; + case 'border-bottom': + if ( + !style['border-bottom-width']?.value || + !style['border-bottom-style']?.value || + !style['border-bottom-color']?.value + ) { + return ''; + } + return `${style['border-bottom-width'].value} ${style['border-bottom-style'].value} ${style['border-bottom-color'].value}`; + case 'background': + if (!style['background-color']?.value && !style['background-image']?.value) { + return ''; + } + return `${style['background-color']?.value} ${style['background-image']?.value} ${style['background-repeat']?.value} ${style['background-attachment']?.value} ${style['background-position']?.value}` + .replace(/ /g, '') + .trim(); + case 'flex': + if ( + !style['flex-grow']?.value || + !style['flex-shrink']?.value || + !style['flex-basis']?.value + ) { + return ''; + } + return `${style['flex-grow'].value} ${style['flex-shrink'].value} ${style['flex-basis'].value}`; + } + + return style[propertyName]?.value || ''; + } +} diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyWriter.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyWriter.ts new file mode 100644 index 000000000..f181c592a --- /dev/null +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyWriter.ts @@ -0,0 +1,415 @@ +import ICSSStyleDeclarationProperty from '../ICSSStyleDeclarationProperty'; +import CSSStyleDeclarationValueParser from './CSSStyleDeclarationValueParser'; +import CSSStyleDeclarationPropertyWriterPropertyNames from './CSSStyleDeclarationPropertyWriterPropertyNames'; + +/** + * Computed style property parser. + */ +export default class CSSStyleDeclarationPropertyWriter { + private properties: { + [k: string]: ICSSStyleDeclarationProperty; + }; + + /** + * Class construtor. + * + * @param properties Properties. + */ + constructor(properties: { [k: string]: ICSSStyleDeclarationProperty }) { + this.properties = properties; + } + + /** + * Removes a property. + * + * @param name Property name. + */ + public remove(name: string): void { + if (CSSStyleDeclarationPropertyWriterPropertyNames[name]) { + for (const propertyName of CSSStyleDeclarationPropertyWriterPropertyNames[name]) { + delete this.properties[propertyName]; + } + } else { + delete this.properties[name]; + } + } + + /** + * Sets a property. + * + * @param property Property. + */ + public set(property: ICSSStyleDeclarationProperty): void { + const properties = this.getProperties(property); + for (const name of Object.keys(properties)) { + this.properties[name] = properties[name]; + } + } + + /** + * Returns properties to set. + * + * @param property Property. + * @returns Properties. + */ + private getProperties(property: ICSSStyleDeclarationProperty): { + [k: string]: ICSSStyleDeclarationProperty; + } { + const { name, important } = property; + let { value } = property; + + switch (name) { + case 'border': + return this.getBorderProperties(property); + case 'border-left': + case 'border-bottom': + case 'border-right': + case 'border-top': + return this.getBorderPositionProperties(property); + case 'border-width': + value = CSSStyleDeclarationValueParser.getBorderWidth(value); + return value + ? { + 'border-top-width': { name, important, value }, + 'border-right-width': { name, important, value }, + 'border-bottom-width': { name, important, value }, + 'border-left-width': { name, important, value } + } + : {}; + case 'border-style': + value = CSSStyleDeclarationValueParser.getBorderStyle(value); + return value + ? { + 'border-top-style': { name, important, value }, + 'border-right-style': { name, important, value }, + 'border-bottom-style': { name, important, value }, + 'border-left-style': { name, important, value } + } + : {}; + case 'border-color': + value = CSSStyleDeclarationValueParser.getBorderCollapse(value); + return value + ? { + 'border-top-color': { name, important, value }, + 'border-right-color': { name, important, value }, + 'border-bottom-color': { name, important, value }, + 'border-left-color': { name, important, value } + } + : {}; + case 'border-radius': + return this.getBorderRadiusProperties(property); + case 'border-collapse': + value = CSSStyleDeclarationValueParser.getBorderCollapse(value); + return value ? { [name]: { name, important, value } } : {}; + case 'clear': + value = CSSStyleDeclarationValueParser.getClear(value); + return value ? { [name]: { name, important, value } } : {}; + case 'clip': + value = CSSStyleDeclarationValueParser.getClip(value); + return value ? { [name]: { name, important, value } } : {}; + case 'css-float': + case 'float': + value = CSSStyleDeclarationValueParser.getFloat(value); + return value ? { [name]: { name, important, value } } : {}; + case 'flex': + return this.getFlexProperties(property); + case 'flex-shrink': + case 'flex-grow': + value = CSSStyleDeclarationValueParser.getInteger(value); + return value ? { [name]: { name, important, value } } : {}; + case 'flex-basis': + value = CSSStyleDeclarationValueParser.getFlexBasis(value); + return value ? { [name]: { name, important, value } } : {}; + case 'padding': + return this.getPaddingProperties(property); + case 'margin': + return this.getMarginProperties(property); + case 'background': + return this.getBackgroundProperties(property); + case 'top': + case 'right': + case 'bottom': + case 'left': + value = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + return value ? { [name]: { name, important, value } } : {}; + case 'padding-top': + case 'padding-bottom': + case 'padding-left': + case 'padding-right': + value = CSSStyleDeclarationValueParser.getMeasurement(value); + return value ? { [name]: { name, important, value } } : {}; + case 'margin-top': + case 'margin-bottom': + case 'margin-left': + case 'margin-right': + value = CSSStyleDeclarationValueParser.getMargin(value); + return value ? { [name]: { name, important, value } } : {}; + case 'border-top-width': + case 'border-bottom-width': + case 'border-left-width': + case 'border-right-width': + value = CSSStyleDeclarationValueParser.getBorderWidth(value); + return value ? { [name]: { name, important, value } } : {}; + case 'font-size': + value = CSSStyleDeclarationValueParser.getFontSize(value); + return value ? { [name]: { name, important, value } } : {}; + case 'color': + case 'flood-color': + case 'border-top-color': + case 'border-bottom-color': + case 'border-left-color': + case 'border-right-color': + value = CSSStyleDeclarationValueParser.getColor(value); + return value ? { [name]: { name, important, value } } : {}; + case 'border-top-style': + case 'border-bottom-style': + case 'border-left-style': + case 'border-right-style': + value = CSSStyleDeclarationValueParser.getBorderStyle(value); + return value ? { [name]: { name, important, value } } : {}; + } + + return { + [name]: { name, important, value } + }; + } + + /** + * Returns border properties. + * + * @param property Property. + * @returns Properties. + */ + private getBorderProperties(property: ICSSStyleDeclarationProperty): { + [k: string]: ICSSStyleDeclarationProperty; + } { + const { name, value, important } = property; + const parts = value.split(/ +/); + + parts[0] = parts[0] ? CSSStyleDeclarationValueParser.getBorderWidth(parts[0]) : ''; + parts[1] = parts[1] ? CSSStyleDeclarationValueParser.getBorderStyle(parts[1]) : ''; + parts[2] = parts[2] ? CSSStyleDeclarationValueParser.getColor(parts[2]) : ''; + + if (!parts[0] || parts[1] === null || parts[2] === null) { + return {}; + } + + return { + 'border-top-width': { name, important, value: parts[0] }, + 'border-right-width': { name, important, value: parts[0] }, + 'border-bottom-width': { name, important, value: parts[0] }, + 'border-left-width': { name, important, value: parts[0] }, + 'border-top-style': { name, important, value: parts[1] }, + 'border-right-style': { name, important, value: parts[1] }, + 'border-bottom-style': { name, important, value: parts[1] }, + 'border-left-style': { name, important, value: parts[1] }, + 'border-top-color': { name, important, value: parts[2] }, + 'border-right-color': { name, important, value: parts[2] }, + 'border-bottom-color': { name, important, value: parts[2] }, + 'border-left-color': { name, important, value: parts[2] } + }; + } + + /** + * Returns border radius properties. + * + * @param property Property. + * @returns Properties. + */ + private getBorderRadiusProperties(property: ICSSStyleDeclarationProperty): { + [k: string]: ICSSStyleDeclarationProperty; + } { + const { name, value, important } = property; + const parts = value.split(/ +/); + parts[0] = parts[0] ? CSSStyleDeclarationValueParser.getMeasurement(parts[0]) : ''; + parts[1] = parts[1] ? CSSStyleDeclarationValueParser.getMeasurement(parts[1]) : ''; + parts[2] = parts[2] ? CSSStyleDeclarationValueParser.getMeasurement(parts[2]) : ''; + parts[3] = parts[3] ? CSSStyleDeclarationValueParser.getMeasurement(parts[3]) : ''; + if (!parts[0] || parts[1] === null || parts[2] === null || parts[3] === null) { + return {}; + } + return { + 'border-top-left-radius': { name, important, value: parts[0] }, + 'border-top-right-radius': { name, important, value: parts[1] }, + 'border-bottom-right-radius': { name, important, value: parts[2] }, + 'border-bottom-left-radius': { name, important, value: parts[3] } + }; + } + + /** + * Returns border position properties. + * + * @param property Property. + * @returns Properties. + */ + private getBorderPositionProperties(property: ICSSStyleDeclarationProperty): { + [k: string]: ICSSStyleDeclarationProperty; + } { + const { name, value, important } = property; + const parts = value.split(/ +/); + parts[0] = parts[0] ? CSSStyleDeclarationValueParser.getBorderWidth(parts[0]) : ''; + parts[1] = parts[1] ? CSSStyleDeclarationValueParser.getBorderStyle(parts[1]) : ''; + parts[2] = parts[2] ? CSSStyleDeclarationValueParser.getColor(parts[2]) : ''; + + if (!parts[0] || parts[1] === null || parts[2] === null) { + return {}; + } + + const borderName = name.split('-')[1]; + + return { + [`border-${borderName}-width`]: { name, important, value: parts[0] }, + [`border-${borderName}-style`]: { name, important, value: parts[1] }, + [`border-${borderName}-color`]: { name, important, value: parts[2] } + }; + } + + /** + * Returns padding properties. + * + * @param property Property. + * @returns Properties. + */ + private getPaddingProperties(property: ICSSStyleDeclarationProperty): { + [k: string]: ICSSStyleDeclarationProperty; + } { + const { name, value, important } = property; + const parts = value.split(/ +/); + parts[0] = parts[0] ? CSSStyleDeclarationValueParser.getMeasurement(parts[0]) : ''; + parts[1] = parts[1] ? CSSStyleDeclarationValueParser.getMeasurement(parts[1]) : ''; + parts[2] = parts[2] ? CSSStyleDeclarationValueParser.getMeasurement(parts[2]) : ''; + parts[3] = parts[3] ? CSSStyleDeclarationValueParser.getMeasurement(parts[3]) : ''; + + if (!parts[0] || parts[1] === null || parts[2] === null || parts[3] === null) { + return {}; + } + + return { + ['padding-top']: { name, important, value: parts[0] }, + ['padding-right']: { name, important, value: parts[1] }, + ['padding-bottom']: { name, important, value: parts[2] }, + ['padding-left']: { name, important, value: parts[3] } + }; + } + + /** + * Returns margin properties. + * + * @param property Property. + * @returns Properties. + */ + private getMarginProperties(property: ICSSStyleDeclarationProperty): { + [k: string]: ICSSStyleDeclarationProperty; + } { + const { name, value, important } = property; + const parts = value.split(/ +/); + parts[0] = parts[0] ? CSSStyleDeclarationValueParser.getMeasurement(parts[0]) : ''; + parts[1] = parts[1] ? CSSStyleDeclarationValueParser.getMeasurement(parts[1]) : ''; + parts[2] = parts[2] ? CSSStyleDeclarationValueParser.getMeasurement(parts[2]) : ''; + parts[3] = parts[3] ? CSSStyleDeclarationValueParser.getMeasurement(parts[3]) : ''; + + if (!parts[0] || parts[1] === null || parts[2] === null || parts[3] === null) { + return {}; + } + + return { + ['padding-top']: { name, important, value: parts[0] }, + ['padding-right']: { name, important, value: parts[1] }, + ['padding-bottom']: { name, important, value: parts[2] }, + ['padding-left']: { name, important, value: parts[3] } + }; + } + + /** + * Returns flex properties. + * + * @param property Property. + * @returns Properties. + */ + private getFlexProperties(property: ICSSStyleDeclarationProperty): { + [k: string]: ICSSStyleDeclarationProperty; + } { + const { name, value, important } = property; + const lowerValue = value.trim().toLowerCase(); + switch (lowerValue) { + case 'none': + return { + 'flex-grow': { name, important, value: '0' }, + 'flex-shrink': { name, important, value: '0' }, + 'flex-basis': { name, important, value: 'auto' } + }; + case 'auto': + return { + 'flex-grow': { name, important, value: '1' }, + 'flex-shrink': { name, important, value: '1' }, + 'flex-basis': { name, important, value: 'auto' } + }; + case 'initial': + return { + 'flex-grow': { name, important, value: '0' }, + 'flex-shrink': { name, important, value: '1' }, + 'flex-basis': { name, important, value: 'auto' } + }; + case 'inherit': + return { + 'flex-grow': { name, important, value: 'inherit' }, + 'flex-shrink': { name, important, value: 'inherit' }, + 'flex-basis': { name, important, value: 'inherit' } + }; + } + + const parts = value.split(/ +/); + parts[0] = parts[0] ? CSSStyleDeclarationValueParser.getInteger(parts[0]) : ''; + parts[1] = parts[1] ? CSSStyleDeclarationValueParser.getInteger(parts[1]) : ''; + parts[2] = parts[2] ? CSSStyleDeclarationValueParser.getFlexBasis(parts[2]) : ''; + + if (!parts[0] || !parts[1] || !parts[2]) { + return {}; + } + + return { + 'flex-grow': { name, important, value: parts[0] }, + 'flex-shrink': { name, important, value: parts[1] }, + 'flex-basis': { name, important, value: parts[2] } + }; + } + + /** + * Returns background properties. + * + * @param property Property. + * @returns Properties. + */ + private getBackgroundProperties(property: ICSSStyleDeclarationProperty): { + [k: string]: ICSSStyleDeclarationProperty; + } { + const { name, value, important } = property; + const parts = value.split(/ +/); + + if (!parts[0]) { + return {}; + } + + // First value can be color or image url + if (!CSSStyleDeclarationValueParser.getColor(parts[0])) { + parts.unshift(''); + } + + parts[0] = parts[0] ? CSSStyleDeclarationValueParser.getColor(parts[0]) : ''; + parts[1] = parts[1] ? CSSStyleDeclarationValueParser.getURL(parts[1]) : ''; + parts[2] = parts[2] ? CSSStyleDeclarationValueParser.getBackgroundRepeat(parts[2]) : ''; + parts[3] = parts[3] ? CSSStyleDeclarationValueParser.getBackgroundAttachment(parts[3]) : ''; + parts[4] = parts[4] ? CSSStyleDeclarationValueParser.getBackgroundPosition(parts[4]) : ''; + if ((!parts[0] && !parts[1]) || parts[2] === null || parts[3] === null || parts[4] === null) { + return {}; + } + + return { + 'background-color': { name, important, value: parts[0] }, + 'background-image': { name, important, value: parts[1] }, + 'background-repeat': { name, important, value: parts[2] }, + 'background-attachment': { name, important, value: parts[3] }, + 'background-position': { name, important, value: parts[4] } + }; + } +} diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyWriterPropertyNames.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyWriterPropertyNames.ts new file mode 100644 index 000000000..63ba2c3ed --- /dev/null +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyWriterPropertyNames.ts @@ -0,0 +1,57 @@ +export default { + border: [ + 'border-top-width', + 'border-right-width', + 'border-bottom-width', + 'border-left-width', + 'border-top-style', + 'border-right-style', + 'border-bottom-style', + 'border-left-style', + 'border-top-color', + 'border-right-color', + 'border-bottom-color', + 'border-left-color' + ], + ['border-left']: ['border-left-width', 'border-left-style', 'border-left-color'], + + ['border-bottom']: ['border-bottom-width', 'border-bottom-style', 'border-bottom-color'], + + ['border-right']: ['border-right-width', 'border-right-style', 'border-right-color'], + + ['border-top']: ['border-top-width', 'border-top-style', 'border-top-color'], + ['border-width']: [ + 'border-top-width', + 'border-right-width', + 'border-bottom-width', + 'border-left-width' + ], + ['border-style']: [ + 'border-top-style', + 'border-right-style', + 'border-bottom-style', + 'border-left-style' + ], + ['border-color']: [ + 'border-top-color', + 'border-right-color', + 'border-bottom-color', + 'border-left-color' + ], + ['border-radius']: [ + 'border-top-left-radius', + 'border-top-right-radius', + 'border-bottom-right-radius', + 'border-bottom-left-radius' + ], + ['background']: [ + 'background-color', + 'background-image', + 'background-repeat', + 'background-attachment', + 'background-position' + ], + ['flex']: ['flex-grow', 'flex-shrink', 'flex-basis'], + ['padding']: ['padding-top', 'padding-right', 'padding-bottom', 'padding-left'], + ['margin']: ['margin-top', 'margin-right', 'margin-bottom', 'margin-left'] +}; diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyParser.ts deleted file mode 100644 index 8b8eaa044..000000000 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyParser.ts +++ /dev/null @@ -1,483 +0,0 @@ -import ICSSStyleDeclarationProperty from '../ICSSStyleDeclarationProperty'; -import CSSStyleDeclarationPropertyValidator from './CSSStyleDeclarationStylePropertyValidator'; - -/** - * Computed style property parser. - */ -export default class CSSStyleDeclarationStylePropertyParser { - /** - * Returns property value. - * - * @param style Style. - * @param propertyName Property name. - * @returns Property value. - */ - public static getPropertyValue( - style: { - [k: string]: ICSSStyleDeclarationProperty; - }, - propertyName: string - ): string { - switch (propertyName) { - case 'margin': - if (!style['margin-top']?.value) { - return ''; - } - return `${style['margin-top']?.value} ${style['margin-right']?.value} ${style['margin-bottom']?.value} ${style['margin-left']?.value}` - .replace(/ /g, '') - .trim(); - case 'padding': - if (!style['padding-top']?.value) { - return ''; - } - return `${style['padding-top']?.value} ${style['padding-right']?.value} ${style['padding-bottom']?.value} ${style['padding-left']?.value}` - .replace(/ /g, '') - .trim(); - case 'border': - if ( - !style['border-top-width']?.value || - !style['border-top-style']?.value || - !style['border-top-color']?.value || - style['border-right-width']?.value !== style['border-top-width']?.value || - style['border-right-style']?.value !== style['border-top-style']?.value || - style['border-right-color']?.value !== style['border-top-color']?.value || - style['border-bottom-width']?.value !== style['border-top-width']?.value || - style['border-bottom-style']?.value !== style['border-top-style']?.value || - style['border-bottom-color']?.value !== style['border-top-color']?.value || - style['border-left-width']?.value !== style['border-top-width']?.value || - style['border-left-style']?.value !== style['border-top-style']?.value || - style['border-left-color']?.value !== style['border-top-color']?.value - ) { - return ''; - } - return `${style['border-top-width'].value} ${style['border-top-style'].value} ${style['border-top-color'].value}`; - case 'border-left': - if ( - !style['border-left-width']?.value || - !style['border-left-style']?.value || - !style['border-left-color']?.value - ) { - return ''; - } - return `${style['border-left-width'].value} ${style['border-left-style'].value} ${style['border-left-color'].value}`; - case 'border-right': - if ( - !style['border-right-width']?.value || - !style['border-right-style']?.value || - !style['border-right-color']?.value - ) { - return ''; - } - return `${style['border-right-width'].value} ${style['border-right-style'].value} ${style['border-right-color'].value}`; - case 'border-top': - if ( - !style['border-top-width']?.value || - !style['border-top-style']?.value || - !style['border-top-color']?.value - ) { - return ''; - } - return `${style['border-top-width'].value} ${style['border-top-style'].value} ${style['border-top-color'].value}`; - case 'border-bottom': - if ( - !style['border-bottom-width']?.value || - !style['border-bottom-style']?.value || - !style['border-bottom-color']?.value - ) { - return ''; - } - return `${style['border-bottom-width'].value} ${style['border-bottom-style'].value} ${style['border-bottom-color'].value}`; - case 'background': - if (!style['background-color']?.value && !style['background-image']?.value) { - return ''; - } - return `${style['background-color']?.value} ${style['background-image']?.value} ${style['background-repeat']?.value} ${style['background-attachment']?.value} ${style['background-position']?.value}` - .replace(/ /g, '') - .trim(); - case 'flex': - if ( - !style['flex-grow']?.value || - !style['flex-shrink']?.value || - !style['flex-basis']?.value - ) { - return ''; - } - return `${style['flex-grow'].value} ${style['flex-shrink'].value} ${style['flex-basis'].value}`; - } - - return style[propertyName]?.value || ''; - } - - /** - * Returns valid properties. - * - * @param property Property. - * @returns Properties. - */ - public static getValidProperties(property: ICSSStyleDeclarationProperty): { - [k: string]: ICSSStyleDeclarationProperty; - } { - const { name, value, important } = property; - let parts; - - switch (name) { - case 'border': - parts = value.split(/ +/); - - if ( - !CSSStyleDeclarationPropertyValidator.validateSize(parts[0]) || - !CSSStyleDeclarationPropertyValidator.validateBorderStyle(parts[1]) || - !CSSStyleDeclarationPropertyValidator.validateColor(parts[2]) - ) { - return {}; - } - - return { - 'border-top-width': { name, important, value: parts[0] }, - 'border-right-width': { name, important, value: parts[0] }, - 'border-bottom-width': { name, important, value: parts[0] }, - 'border-left-width': { name, important, value: parts[0] }, - 'border-top-style': { name, important, value: parts[1] }, - 'border-right-style': { name, important, value: parts[1] }, - 'border-bottom-style': { name, important, value: parts[1] }, - 'border-left-style': { name, important, value: parts[1] }, - 'border-top-color': { name, important, value: parts[2] }, - 'border-right-color': { name, important, value: parts[2] }, - 'border-bottom-color': { name, important, value: parts[2] }, - 'border-left-color': { name, important, value: parts[2] } - }; - case 'border-left': - case 'border-bottom': - case 'border-right': - case 'border-top': - parts = value.split(/ +/); - - if ( - !CSSStyleDeclarationPropertyValidator.validateSize(parts[0]) || - !CSSStyleDeclarationPropertyValidator.validateBorderStyle(parts[1]) || - !CSSStyleDeclarationPropertyValidator.validateColor(parts[2]) - ) { - return {}; - } - - const borderName = name.split('-')[1]; - - return { - [`border-${borderName}-width`]: { name, important, value: parts[0] }, - [`border-${borderName}-style`]: { name, important, value: parts[1] }, - [`border-${borderName}-color`]: { name, important, value: parts[2] } - }; - case 'border-width': - if (!CSSStyleDeclarationPropertyValidator.validateSize(value)) { - return {}; - } - return { - 'border-top-width': { name, important, value }, - 'border-right-width': { name, important, value }, - 'border-bottom-width': { name, important, value }, - 'border-left-width': { name, important, value } - }; - case 'border-style': - if (!CSSStyleDeclarationPropertyValidator.validateBorderStyle(value)) { - return {}; - } - return { - 'border-top-style': { name, important, value }, - 'border-right-style': { name, important, value }, - 'border-bottom-style': { name, important, value }, - 'border-left-style': { name, important, value } - }; - case 'border-color': - if (!CSSStyleDeclarationPropertyValidator.validateColor(value)) { - return {}; - } - return { - 'border-top-color': { name, important, value }, - 'border-right-color': { name, important, value }, - 'border-bottom-color': { name, important, value }, - 'border-left-color': { name, important, value } - }; - case 'border-radius': - parts = value.split(/ +/); - if ( - !CSSStyleDeclarationPropertyValidator.validateSize(parts[0]) || - (parts[1] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[1])) || - (parts[2] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[2])) || - (parts[3] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[3])) - ) { - return {}; - } - return { - 'border-top-left-radius': { name, important, value: parts[0] || '' }, - 'border-top-right-radius': { name, important, value: parts[1] || '' }, - 'border-bottom-right-radius': { name, important, value: parts[2] || '' }, - 'border-bottom-left-radius': { name, important, value: parts[3] || '' } - }; - case 'border-collapse': - if (!value || !CSSStyleDeclarationPropertyValidator.validateBorderCollapse(value)) { - return {}; - } - return { - [name]: { name, important, value } - }; - case 'clear': - if (!value || !CSSStyleDeclarationPropertyValidator.validateClear(value)) { - return {}; - } - return { - [name]: { name, important, value } - }; - case 'clip': - if (!value || !CSSStyleDeclarationPropertyValidator.validateClip(value)) { - return {}; - } - return { - [name]: { name, important, value } - }; - case 'css-float': - case 'float': - if (!value || !CSSStyleDeclarationPropertyValidator.validateFloat(value)) { - return {}; - } - return { - [name]: { name, important, value } - }; - case 'flex': - const lowerValue = value.trim().toLowerCase(); - switch (lowerValue) { - case 'none': - return { - 'flex-grow': { name, important, value: '0' }, - 'flex-shrink': { name, important, value: '0' }, - 'flex-basis': { name, important, value: 'auto' } - }; - case 'auto': - return { - 'flex-grow': { name, important, value: '1' }, - 'flex-shrink': { name, important, value: '1' }, - 'flex-basis': { name, important, value: 'auto' } - }; - case 'initial': - return { - 'flex-grow': { name, important, value: '0' }, - 'flex-shrink': { name, important, value: '1' }, - 'flex-basis': { name, important, value: 'auto' } - }; - case 'inherit': - return { - 'flex-grow': { name, important, value: 'inherit' }, - 'flex-shrink': { name, important, value: 'inherit' }, - 'flex-basis': { name, important, value: 'inherit' } - }; - } - - parts = value.split(/ +/); - if ( - !CSSStyleDeclarationPropertyValidator.validateInteger(parts[0]) || - !CSSStyleDeclarationPropertyValidator.validateInteger(parts[1]) || - (parts[2] !== 'auto' && - parts[2] !== 'inherit' && - !CSSStyleDeclarationPropertyValidator.validateSize(parts[2])) - ) { - return {}; - } - return { - 'flex-grow': { name, important, value: parts[0] }, - 'flex-shrink': { name, important, value: parts[1] }, - 'flex-basis': { name, important, value: parts[2] } - }; - case 'flex-shrink': - case 'flex-grow': - if (!value || !CSSStyleDeclarationPropertyValidator.validateInteger(value)) { - return {}; - } - return { - [name]: { name, important, value } - }; - case 'flex-basis': - if ( - value !== 'auto' && - value !== 'inherit' && - !CSSStyleDeclarationPropertyValidator.validateSize(value) - ) { - return {}; - } - return { - [name]: { name, important, value } - }; - case 'padding': - case 'margin': - parts = value.split(/ +/); - if ( - !parts[0] || - !CSSStyleDeclarationPropertyValidator.validateSize(parts[0]) || - (parts[1] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[1])) || - (parts[2] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[2])) || - (parts[3] && !CSSStyleDeclarationPropertyValidator.validateSize(parts[3])) - ) { - return {}; - } - return { - [`${name}-top`]: { name, important, value: parts[0] }, - [`${name}-right`]: { name, important, value: parts[1] || '' }, - [`${name}-bottom`]: { name, important, value: parts[2] || '' }, - [`${name}-left`]: { name, important, value: parts[3] || '' } - }; - case 'background': - parts = value.split(/ +/); - // First value can be color or image url - if (!CSSStyleDeclarationPropertyValidator.validateColor(parts[0])) { - parts.unshift(''); - } - if ( - (!parts[0] && !parts[1]) || - (parts[0] && !CSSStyleDeclarationPropertyValidator.validateColor(parts[0])) || - (parts[1] && !CSSStyleDeclarationPropertyValidator.validateURL(parts[1])) || - (parts[2] && !CSSStyleDeclarationPropertyValidator.validateBackgroundRepeat(parts[2])) || - (parts[3] && - !CSSStyleDeclarationPropertyValidator.validateBackgroundAttachment(parts[3])) || - (parts[4] && !CSSStyleDeclarationPropertyValidator.validateBackgroundPosition(parts[4])) - ) { - return {}; - } - return { - 'background-color': { name, important, value: parts[0] || '' }, - 'background-image': { name, important, value: parts[1] || '' }, - 'background-repeat': { name, important, value: parts[2] || '' }, - 'background-attachment': { name, important, value: parts[3] || '' }, - 'background-position': { name, important, value: parts[4] || '' } - }; - case 'top': - case 'right': - case 'bottom': - case 'left': - case 'padding-top': - case 'padding-bottom': - case 'padding-left': - case 'padding-right': - case 'margin-top': - case 'margin-bottom': - case 'margin-left': - case 'margin-right': - case 'border-top-width': - case 'border-bottom-width': - case 'border-left-width': - case 'border-right-width': - case 'font-size': - if (!CSSStyleDeclarationPropertyValidator.validateSize(value)) { - return {}; - } - return { - [name]: { name, important, value } - }; - case 'color': - case 'flood-color': - case 'border-top-color': - case 'border-bottom-color': - case 'border-left-color': - case 'border-right-color': - if (!CSSStyleDeclarationPropertyValidator.validateColor(value)) { - return {}; - } - return { - [name]: { name, important, value } - }; - case 'border-top-style': - case 'border-bottom-style': - case 'border-left-style': - case 'border-right-style': - if (!CSSStyleDeclarationPropertyValidator.validateBorderStyle(value)) { - return {}; - } - return { - [name]: { name, important, value } - }; - } - - return { - [name]: { name, important, value } - }; - } - - /** - * Returns related property names. - * - * @param propertyName Property name. - * @returns Properties. - */ - public static getRelatedPropertyNames(propertyName: string): string[] { - const name = propertyName; - - switch (name) { - case 'border': - return [ - 'border-top-width', - 'border-right-width', - 'border-bottom-width', - 'border-left-width', - 'border-top-style', - 'border-right-style', - 'border-bottom-style', - 'border-left-style', - 'border-top-color', - 'border-right-color', - 'border-bottom-color', - 'border-left-color' - ]; - case 'border-left': - case 'border-bottom': - case 'border-right': - case 'border-top': - const borderName = name.split('-')[1]; - - return [ - `border-${borderName}-width`, - `border-${borderName}-style`, - `border-${borderName}-color` - ]; - case 'border-width': - return [ - 'border-top-width', - 'border-right-width', - 'border-bottom-width', - 'border-left-width' - ]; - case 'border-style': - return [ - 'border-top-style', - 'border-right-style', - 'border-bottom-style', - 'border-left-style' - ]; - case 'border-color': - return [ - 'border-top-color', - 'border-right-color', - 'border-bottom-color', - 'border-left-color' - ]; - case 'border-radius': - return [ - 'border-top-left-radius', - 'border-top-right-radius', - 'border-bottom-right-radius', - 'border-bottom-left-radius' - ]; - case 'background': - return [ - 'background-color', - 'background-image', - 'background-repeat', - 'background-attachment', - 'background-position' - ]; - case 'flex': - return ['flex-grow', 'flex-shrink', 'flex-basis']; - case 'padding': - case 'margin': - return [`${name}-top`, `${name}-right`, `${name}-bottom`, `${name}-left`]; - } - - return [name]; - } -} diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyValidator.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyValidator.ts deleted file mode 100644 index b09b73fa6..000000000 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStylePropertyValidator.ts +++ /dev/null @@ -1,239 +0,0 @@ -const COLOR_REGEXP = - /^#([0-9a-fA-F]{3,4}){1,2}$|^rgb\(([^)]*)\)$|^rgba\(([^)]*)\)$|^hsla?\(\s*(-?\d+|-?\d*.\d+)\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)%\s*(,\s*(-?\d+|-?\d*.\d+)\s*)?\)/; - -const SIZE_REGEXP = - /^(0|[-+]?[0-9]*\.?[0-9]+(in|cm|em|mm|pt|pc|px|ex|rem|vh|vw|ch))$|^[-+]?[0-9]*\.?[0-9]+%$/; -const URL_REGEXP = /^url\(\s*([^)]*)\s*\)$/; -const RECT_REGEXP = /^rect\((.*)\)$/i; -const BORDER_STYLE = [ - 'none', - 'hidden', - 'dotted', - 'dashed', - 'solid', - 'double', - 'groove', - 'ridge', - 'inset', - 'outset' -]; -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 BACKGROUND_POSITIONS = ['top', 'center', 'bottom', 'left', 'right', 'inherit']; -const CLEAR = ['none', 'left', 'right', 'both', 'inherit']; -const FLOAT = ['none', 'left', 'right', 'inherit']; - -/** - * Computed style property parser. - */ -export default class CSSStyleDeclarationStylePropertyValidator { - /** - * Validates size. - * - * @param value Value. - * @returns "true" if valid. - */ - public static validateSize(value: string): boolean { - return SIZE_REGEXP.test(value); - } - - /** - * Validates integer. - * - * @param value Value. - * @returns "true" if valid. - */ - public static validateInteger(value: string): boolean { - return !isNaN(parseInt(value)); - } - - /** - * Validates color. - * - * @param value Value. - * @returns "true" if valid. - */ - public static validateColor(value: string): boolean { - return COLOR_REGEXP.test(value); - } - - /** - * Validates URL. - * - * Based on: - * https://github.com/jsdom/cssstyle/blob/master/lib/parsers.js#L222 - * - * @param value Value. - * @returns "true" if valid. - */ - public static validateURL(value: string): boolean { - if (!value || value === 'none' || value === 'inherit') { - return false; - } - - const result = URL_REGEXP.exec(value); - - if (!result) { - return false; - } - - let url = result[1]; - - if ((url[0] === '"' || url[0] === "'") && url[0] !== url[url.length - 1]) { - return false; - } - - if (url[0] === '"' || url[0] === "'") { - url = url.substring(1, url.length - 1); - } - - for (let i = 0; i < url.length; i++) { - switch (url[i]) { - case '(': - case ')': - case ' ': - case '\t': - case '\n': - case "'": - case '"': - return false; - case '\\': - i++; - break; - } - } - - return true; - } - - /** - * Validates border style. - * - * @param value Value. - * @returns "true" if valid. - */ - public static validateBorderStyle(value: string): boolean { - return BORDER_STYLE.includes(value.toLowerCase()); - } - - /** - * Validates border collapse. - * - * @param value Value. - * @returns "true" if valid. - */ - public static validateBorderCollapse(value: string): boolean { - return BORDER_COLLAPSE.includes(value.toLowerCase()); - } - - /** - * Validates background repeat. - * - * @param value Value. - * @returns "true" if valid. - */ - public static validateBackgroundRepeat(value: string): boolean { - return BACKGROUND_REPEAT.includes(value.toLowerCase()); - } - - /** - * Validates background attachment. - * - * @param value Value. - * @returns "true" if valid. - */ - public static validateBackgroundAttachment(value: string): boolean { - return BACKGROUND_ATTACHMENT.includes(value.toLowerCase()); - } - - /** - * Validates URL. - * - * Based on: - * https://github.com/jsdom/cssstyle/blob/master/lib/properties/backgroundPosition.js - * - * @param value Value. - * @returns "true" if valid. - */ - public static validateBackgroundPosition(value: string): boolean { - if (!value) { - return false; - } - const parts = value.split(/\s+/); - if (parts.length > 2 || parts.length < 1) { - return false; - } - if (parts.length === 1) { - if (this.validateSize(parts[0])) { - return true; - } - if (parts[0]) { - if (BACKGROUND_POSITIONS.includes(value.toLowerCase())) { - return true; - } - } - return false; - } - if (this.validateSize(parts[0]) && this.validateSize(parts[1])) { - return true; - } - if ( - BACKGROUND_POSITIONS.includes(parts[0].toLowerCase()) && - BACKGROUND_POSITIONS.includes(parts[1].toLowerCase()) - ) { - return true; - } - return false; - } - - /** - * Validates clear. - * - * @param value Value. - * @returns "true" if valid. - */ - public static validateClear(value: string): boolean { - return CLEAR.includes(value.toLowerCase()); - } - - /** - * Validates clip - * - * Based on: - * https://github.com/jsdom/cssstyle/blob/master/lib/properties/clip.js - * - * @param value Value. - * @returns "true" if valid. - */ - public static validateClip(value: string): boolean { - const lowerValue = value.toLowerCase(); - if (lowerValue === 'auto' || lowerValue === 'initial' || lowerValue === 'inherit') { - return true; - } - const matches = lowerValue.match(RECT_REGEXP); - if (!matches) { - return false; - } - const parts = matches[1].split(/\s*,\s*/); - if (parts.length !== 4) { - return false; - } - for (const part of parts) { - if (!this.validateSize(part)) { - return false; - } - } - return true; - } - - /** - * Validates float. - * - * @param value Value. - * @returns "true" if valid. - */ - public static validateFloat(value: string): boolean { - return FLOAT.includes(value.toLowerCase()); - } -} diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStyleParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStyleString.ts similarity index 84% rename from packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStyleParser.ts rename to packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStyleString.ts index 3f38c8ded..8ddf700c2 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStyleParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStyleString.ts @@ -1,11 +1,11 @@ import IElement from '../../../nodes/element/IElement'; import ICSSStyleDeclarationProperty from '../ICSSStyleDeclarationProperty'; -import CSSStyleDeclarationPropertyParser from './CSSStyleDeclarationStylePropertyParser'; +import CSSStyleDeclarationPropertyWriter from './CSSStyleDeclarationPropertyWriter'; /** * CSS Style Declaration utility */ -export default class CSSStyleDeclarationStyleParser { +export default class CSSStyleDeclarationStyleString { /** * Returns a style from a string. * @@ -17,6 +17,7 @@ export default class CSSStyleDeclarationStyleParser { } { const style = {}; const parts = styleString.split(';'); + const writer = new CSSStyleDeclarationPropertyWriter(style); for (const part of parts) { if (part) { @@ -29,14 +30,11 @@ export default class CSSStyleDeclarationStyleParser { const valueWithoutImportant = trimmedValue.replace(' !important', ''); if (valueWithoutImportant) { - Object.assign( - style, - CSSStyleDeclarationPropertyParser.getValidProperties({ - name: trimmedName, - value: valueWithoutImportant, - important - }) - ); + writer.set({ + name: trimmedName, + value: valueWithoutImportant, + important + }); } } } @@ -46,27 +44,6 @@ export default class CSSStyleDeclarationStyleParser { return style; } - /** - * 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; - } - /** * Returns element style properties. * @@ -89,4 +66,25 @@ export default class CSSStyleDeclarationStyleParser { 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 new file mode 100644 index 000000000..e7819ee50 --- /dev/null +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts @@ -0,0 +1,420 @@ +const COLOR_REGEXP = + /^#([0-9a-fA-F]{3,4}){1,2}$|^rgb\(([^)]*)\)$|^rgba\(([^)]*)\)$|^hsla?\(\s*(-?\d+|-?\d*.\d+)\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)%\s*(,\s*(-?\d+|-?\d*.\d+)\s*)?\)/; + +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 URL_REGEXP = /^url\(\s*([^)]*)\s*\)$/; +const RECT_REGEXP = /^rect\((.*)\)$/i; +const BORDER_STYLE = [ + 'none', + 'hidden', + 'dotted', + 'dashed', + 'solid', + 'double', + 'groove', + 'ridge', + '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 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 FONT_SIZE = [ + 'xx-small', + 'x-small', + 'small', + 'medium', + 'large', + 'x-large', + 'xx-large', + 'xxx-large', + 'smaller', + 'larger', + 'inherit', + 'initial', + 'revert', + 'unset' +]; + +/** + * Computed style property parser. + */ +export default class CSSStyleDeclarationValueParser { + /** + * Returns length. + * + * @param value Value. + * @returns Parsed value. + */ + public static getLength(value: string): string { + if (value === '0') { + return '0px'; + } + if (LENGTH_REGEXP.test(value)) { + return value; + } + return null; + } + + /** + * Returns percentance. + * + * @param value Value. + * @returns Parsed value. + */ + public static getPercentage(value: string): string { + if (value === '0') { + return '0%'; + } + if (PERCENTAGE_REGEXP.test(value)) { + return value; + } + return null; + } + + /** + * Returns measurement. + * + * @param value Value. + * @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); + } + + /** + * Returns measurement or auto. + * + * @param value Value. + * @returns Parsed value. + */ + public static getMeasurementOrAuto(value: string): string { + const lowerValue = value.toLowerCase(); + if (lowerValue === 'auto') { + return lowerValue; + } + return this.getMeasurement(value); + } + + /** + * Returns integer. + * + * @param value Value. + * @returns Parsed value. + */ + public static getInteger(value: string): string { + if (!isNaN(parseInt(value))) { + return value; + } + return null; + } + + /** + * Returns color. + * + * @param value Value. + * @returns Parsed value. + */ + public static getColor(value: string): string { + if (COLOR_REGEXP.test(value)) { + return value; + } + return null; + } + + /** + * Returns URL. + * + * Based on: + * https://github.com/jsdom/cssstyle/blob/master/lib/parsers.js#L222 + * + * @param value Value. + * @returns Parsed value. + */ + public static getURL(value: string): string { + if (!value) { + return null; + } + + const lowerValue = value.toLowerCase(); + + if (lowerValue === 'none' || lowerValue === 'inherit') { + return lowerValue; + } + + const result = URL_REGEXP.exec(value); + + if (!result) { + return null; + } + + let url = result[1]; + + if ((url[0] === '"' || url[0] === "'") && url[0] !== url[url.length - 1]) { + return null; + } + + if (url[0] === '"' || url[0] === "'") { + url = url.substring(1, url.length - 1); + } + + for (let i = 0; i < url.length; i++) { + switch (url[i]) { + case '(': + case ')': + case ' ': + case '\t': + case '\n': + case "'": + case '"': + return null; + case '\\': + i++; + break; + } + } + + return value; + } + + /** + * Returns border style. + * + * @param value Value. + * @returns Parsed value. + */ + public static getBorderStyle(value: string): string { + const lowerValue = value.toLowerCase(); + if (BORDER_STYLE.includes(lowerValue)) { + return lowerValue; + } + return null; + } + + /** + * Returns border collapse. + * + * @param value Value. + * @returns Parsed value. + */ + public static getBorderCollapse(value: string): string { + const lowerValue = value.toLowerCase(); + if (BORDER_COLLAPSE.includes(lowerValue)) { + return lowerValue; + } + return null; + } + + /** + * Returns background repeat. + * + * @param value Value. + * @returns Parsed value. + */ + public static getBackgroundRepeat(value: string): string { + const lowerValue = value.toLowerCase(); + if (BACKGROUND_REPEAT.includes(lowerValue)) { + return lowerValue; + } + return null; + } + + /** + * Returns background attachment. + * + * @param value Value. + * @returns Parsed value. + */ + public static getBackgroundAttachment(value: string): string { + const lowerValue = value.toLowerCase(); + if (BACKGROUND_ATTACHMENT.includes(lowerValue)) { + return lowerValue; + } + return null; + } + + /** + * Returns URL. + * + * Based on: + * https://github.com/jsdom/cssstyle/blob/master/lib/properties/backgroundPosition.js + * + * @param value Value. + * @returns Parsed value. + */ + public static getBackgroundPosition(value: string): string { + if (!value) { + return null; + } + const parts = value.split(/\s+/); + if (parts.length > 2 || parts.length < 1) { + return null; + } + if (parts.length === 1) { + if (this.getLength(parts[0])) { + return value; + } + if (parts[0]) { + const lowerValue = value.toLowerCase(); + if (BACKGROUND_POSITION.includes(lowerValue) || lowerValue === 'inherit') { + return lowerValue; + } + } + return null; + } + if ( + (this.getLength(parts[0]) || this.getPercentage(parts[0])) && + (this.getLength(parts[1]) || this.getPercentage(parts[1])) + ) { + return value.toLowerCase(); + } + if ( + BACKGROUND_POSITION.includes(parts[0].toLowerCase()) && + BACKGROUND_POSITION.includes(parts[1].toLowerCase()) + ) { + return `${parts[0].toLowerCase()} ${parts[1].toLowerCase()}`; + } + return null; + } + + /** + * Returns flex basis. + * + * @param value Value. + * @returns Parsed value. + */ + public static getFlexBasis(value: string): string { + const lowerValue = value.toLowerCase(); + if (FLEX_BASIS.includes(lowerValue)) { + return lowerValue; + } + return this.getLength(value); + } + + /** + * Returns margin. + * + * @param value Value. + * @returns Parsed value. + */ + public static getMargin(value: string): string { + const lowerValue = value.toLowerCase(); + if (lowerValue === 'auto') { + return lowerValue; + } + return this.getMeasurement(value); + } + + /** + * Returns border width. + * + * @param value Value. + * @returns Parsed value. + */ + public static getBorderWidth(value: string): string { + const lowerValue = value.toLowerCase(); + if (BORDER_WIDTH.includes(lowerValue)) { + return lowerValue; + } + return this.getLength(value); + } + + /** + * Returns clear. + * + * @param value Value. + * @returns Parsed value. + */ + public static getClear(value: string): string { + const lowerValue = value.toLowerCase(); + if (CLEAR.includes(lowerValue)) { + return lowerValue; + } + return null; + } + + /** + * Returns clip + * + * Based on: + * https://github.com/jsdom/cssstyle/blob/master/lib/properties/clip.js + * + * @param value Value. + * @returns Parsed value. + */ + public static getClip(value: string): string { + const lowerValue = value.toLowerCase(); + if (lowerValue === 'auto' || lowerValue === 'initial' || lowerValue === 'inherit') { + return lowerValue; + } + const matches = lowerValue.match(RECT_REGEXP); + if (!matches) { + return null; + } + const parts = matches[1].split(/\s*,\s*/); + if (parts.length !== 4) { + return null; + } + for (const part of parts) { + if (!this.getMeasurement(part)) { + return null; + } + } + return value; + } + + /** + * Returns float. + * + * @param value Value. + * @returns Parsed value. + */ + public static getFloat(value: string): string { + const lowerValue = value.toLowerCase(); + if (FLOAT.includes(lowerValue)) { + return lowerValue; + } + return null; + } + + /** + * Returns font size. + * + * @param value Value. + * @returns Parsed value. + */ + public static getFontSize(value: string): string { + const lowerValue = value.toLowerCase(); + if (FONT_SIZE.includes(lowerValue)) { + return lowerValue; + } + return this.getLength(value) || this.getPercentage(value); + } +} From 9b6522f55e21542b5013a16ed128ccf14032ed48 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Thu, 11 Aug 2022 18:22:49 +0200 Subject: [PATCH 15/84] #344@trivial: Continue on CSSStyleDeclaration. --- packages/happy-dom/src/css/.DS_Store | Bin 0 -> 6148 bytes .../AbstractCSSStyleDeclaration.ts | 10 +- .../CSSStyleDeclarationPropertyManager.ts | 559 ++++++++++++++++++ .../CSSStyleDeclarationPropertyWriter.ts | 415 ------------- .../CSSStyleDeclarationStyleString.ts | 4 +- 5 files changed, 566 insertions(+), 422 deletions(-) create mode 100644 packages/happy-dom/src/css/.DS_Store create mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts delete mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyWriter.ts diff --git a/packages/happy-dom/src/css/.DS_Store b/packages/happy-dom/src/css/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ff9b19d703306c2f80387385a0162025aa0fa8cd GIT binary patch literal 6148 zcmeHK!Ab)$5S_HuZYe?!3OxqA7OZU*ikGF*t7nSU*h*T zlVp`rd+;JsW?=FrlbH>9SuzO#h~6|j1gHUkgGyMaVDp2}IO&QMtcOtOZ}>003b(Yc-(a4Jq^ zP^-`9?bcC4wmaQLL(aRcW)-#&N!()wn1PLAK$QAkzlTe*wRL52RBI*b9V!XMWd^@e(9oqA fW2qF^QMI67l7Z-2%nYIjg)ah{25y*vKV{$(o^Vc! literal 0 HcmV?d00001 diff --git a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts index c75774fd6..e5241f65e 100644 --- a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts +++ b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts @@ -4,7 +4,7 @@ import CSSRule from '../CSSRule'; import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum'; import DOMException from '../../exception/DOMException'; import CSSStyleDeclarationStyleString from './utilities/CSSStyleDeclarationStyleString'; -import CSSStyleDeclarationPropertyWriter from './utilities/CSSStyleDeclarationPropertyWriter'; +import CSSStyleDeclarationPropertyManager from './utilities/CSSStyleDeclarationPropertyManager'; import ICSSStyleDeclarationProperty from './ICSSStyleDeclarationProperty'; import CSSStyleDeclarationPropertyReader from './utilities/CSSStyleDeclarationPropertyReader'; @@ -155,7 +155,7 @@ export default abstract class AbstractCSSStyleDeclaration { Object.assign( elementStyleProperties, - CSSStyleDeclarationPropertyWriter.getRelatedProperties({ + CSSStyleDeclarationPropertyManager.getRelatedProperties({ name: propertyName, value, important: !!priority @@ -167,7 +167,7 @@ export default abstract class AbstractCSSStyleDeclaration { } else { Object.assign( this._styles, - CSSStyleDeclarationPropertyWriter.getRelatedProperties({ + CSSStyleDeclarationPropertyManager.getRelatedProperties({ name: propertyName, value, important: !!priority @@ -198,7 +198,7 @@ export default abstract class AbstractCSSStyleDeclaration { this._computed ); const propertiesToRemove = - CSSStyleDeclarationPropertyWriter.getRelatedPropertyNames(propertyName); + CSSStyleDeclarationPropertyManager.getRelatedPropertyNames(propertyName); for (const property of Object.keys(propertiesToRemove)) { delete elementStyleProperties[property]; @@ -214,7 +214,7 @@ export default abstract class AbstractCSSStyleDeclaration { } } else { const propertiesToRemove = - CSSStyleDeclarationPropertyWriter.getRelatedPropertyNames(propertyName); + CSSStyleDeclarationPropertyManager.getRelatedPropertyNames(propertyName); for (const property of Object.keys(propertiesToRemove)) { delete this._styles[property]; diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts new file mode 100644 index 000000000..f4a32b832 --- /dev/null +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts @@ -0,0 +1,559 @@ +import ICSSStyleDeclarationProperty from '../ICSSStyleDeclarationProperty'; +import CSSStyleDeclarationValueParser from './CSSStyleDeclarationValueParser'; +import CSSStyleDeclarationPropertyManagerPropertyNames from './CSSStyleDeclarationPropertyManagerPropertyNames'; + +/** + * Computed this.properties property parser. + */ +export default class CSSStyleDeclarationPropertyManager { + private properties: { + [k: string]: ICSSStyleDeclarationProperty; + } = {}; + + /** + * Class construtor. + * + * @param [cssString] CSS string. + */ + constructor(cssString?: string) { + if (cssString) { + const parts = cssString.split(';'); + + 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) { + this.set(trimmedName, valueWithoutImportant, important); + } + } + } + } + } + } + } + + /** + * Returns property value. + * + * @param name Property name. + * @returns Property value. + */ + public get(name: string): string { + 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-bottom']?.value} ${this.properties['margin-left']?.value}` + .replace(/ /g, '') + .trim(); + case 'padding': + if (!this.properties['padding-top']?.value) { + return ''; + } + return `${this.properties['padding-top']?.value} ${this.properties['padding-right']?.value} ${this.properties['padding-bottom']?.value} ${this.properties['padding-left']?.value}` + .replace(/ /g, '') + .trim(); + case 'border': + if ( + !this.properties['border-top-width']?.value || + !this.properties['border-top-style']?.value || + !this.properties['border-top-color']?.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}`; + case 'border-left': + if ( + !this.properties['border-left-width']?.value || + !this.properties['border-left-style']?.value || + !this.properties['border-left-color']?.value + ) { + return ''; + } + return `${this.properties['border-left-width'].value} ${this.properties['border-left-style'].value} ${this.properties['border-left-color'].value}`; + case 'border-right': + if ( + !this.properties['border-right-width']?.value || + !this.properties['border-right-style']?.value || + !this.properties['border-right-color']?.value + ) { + return ''; + } + return `${this.properties['border-right-width'].value} ${this.properties['border-right-style'].value} ${this.properties['border-right-color'].value}`; + case 'border-top': + if ( + !this.properties['border-top-width']?.value || + !this.properties['border-top-style']?.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}`; + case 'border-bottom': + if ( + !this.properties['border-bottom-width']?.value || + !this.properties['border-bottom-style']?.value || + !this.properties['border-bottom-color']?.value + ) { + return ''; + } + return `${this.properties['border-bottom-width'].value} ${this.properties['border-bottom-style'].value} ${this.properties['border-bottom-color'].value}`; + 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-attachment']?.value} ${this.properties['background-position']?.value}` + .replace(/ /g, '') + .trim(); + 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 this.properties[name]?.value || ''; + } + + /** + * Removes a property. + * + * @param name Property name. + */ + public remove(name: string): void { + if (CSSStyleDeclarationPropertyManagerPropertyNames[name]) { + for (const propertyName of CSSStyleDeclarationPropertyManagerPropertyNames[name]) { + delete this.properties[propertyName]; + } + } else { + delete this.properties[name]; + } + } + + /** + * Sets a property + * + * @param name Name. + * @param value Value. + * @param important Important. + */ + public set(name: string, value: string, important: boolean): void { + switch (name) { + case 'border': + this.setBorder(name, value, important); + break; + case 'border-left': + case 'border-bottom': + case 'border-right': + case 'border-top': + this.setBorderPosition(name, value, important); + break; + case 'border-width': + value = CSSStyleDeclarationValueParser.getBorderWidth(value); + if (value) { + this.properties['border-top-width'] = { name, important, value }; + this.properties['border-right-width'] = { name, important, value }; + this.properties['border-bottom-width'] = { name, important, value }; + this.properties['border-left-width'] = { name, important, value }; + } + break; + case 'border-style': + value = CSSStyleDeclarationValueParser.getBorderStyle(value); + if (value) { + this.properties['border-top-style'] = { name, important, value }; + this.properties['border-right-style'] = { name, important, value }; + this.properties['border-bottom-style'] = { name, important, value }; + this.properties['border-left-style'] = { name, important, value }; + } + break; + case 'border-color': + value = CSSStyleDeclarationValueParser.getBorderCollapse(value); + if (value) { + this.properties['border-top-color'] = { name, important, value }; + this.properties['border-right-color'] = { name, important, value }; + this.properties['border-bottom-color'] = { name, important, value }; + this.properties['border-left-color'] = { name, important, value }; + } + break; + case 'border-radius': + this.setBorderRadius(name, value, important); + break; + case 'border-collapse': + value = CSSStyleDeclarationValueParser.getBorderCollapse(value); + if (value) { + this.properties[name] = { name, important, value }; + } + break; + case 'clear': + value = CSSStyleDeclarationValueParser.getClear(value); + if (value) { + this.properties[name] = { name, important, value }; + } + case 'clip': + value = CSSStyleDeclarationValueParser.getClip(value); + if (value) { + this.properties[name] = { name, important, value }; + } + break; + case 'css-float': + case 'float': + value = CSSStyleDeclarationValueParser.getFloat(value); + if (value) { + this.properties[name] = { name, important, value }; + } + break; + case 'flex': + this.setFlex(name, value, important); + break; + case 'flex-shrink': + case 'flex-grow': + value = CSSStyleDeclarationValueParser.getInteger(value); + if (value) { + this.properties[name] = { name, important, value }; + } + break; + case 'flex-basis': + value = CSSStyleDeclarationValueParser.getFlexBasis(value); + if (value) { + this.properties[name] = { name, important, value }; + } + break; + case 'padding': + this.setPadding(name, value, important); + break; + case 'margin': + this.setMargin(name, value, important); + break; + case 'background': + this.setBackground(name, value, important); + break; + case 'top': + case 'right': + case 'bottom': + case 'left': + value = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + if (value) { + this.properties[name] = { name, important, value }; + } + break; + case 'padding-top': + case 'padding-bottom': + case 'padding-left': + case 'padding-right': + value = CSSStyleDeclarationValueParser.getMeasurement(value); + if (value) { + this.properties[name] = { name, important, value }; + } + break; + case 'margin-top': + case 'margin-bottom': + case 'margin-left': + case 'margin-right': + value = CSSStyleDeclarationValueParser.getMargin(value); + if (value) { + this.properties[name] = { name, important, value }; + } + break; + case 'border-top-width': + case 'border-bottom-width': + case 'border-left-width': + case 'border-right-width': + value = CSSStyleDeclarationValueParser.getBorderWidth(value); + if (value) { + this.properties[name] = { name, important, value }; + } + break; + case 'font-size': + value = CSSStyleDeclarationValueParser.getFontSize(value); + if (value) { + this.properties[name] = { name, important, value }; + } + break; + case 'color': + case 'flood-color': + case 'border-top-color': + case 'border-bottom-color': + case 'border-left-color': + case 'border-right-color': + value = CSSStyleDeclarationValueParser.getColor(value); + if (value) { + this.properties[name] = { name, important, value }; + } + break; + case 'border-top-style': + case 'border-bottom-style': + case 'border-left-style': + case 'border-right-style': + value = CSSStyleDeclarationValueParser.getBorderStyle(value); + if (value) { + this.properties[name] = { name, important, value }; + } + break; + default: + if (value) { + this.properties[name] = { name, important, value }; + } + break; + } + } + + /** + * Sets border. + * + * @param name Name. + * @param value Value. + * @param important Important. + */ + private setBorder(name: string, value: string, important: boolean): void { + const parts = value.split(/ +/); + const borderWidth = parts[0] ? CSSStyleDeclarationValueParser.getBorderWidth(parts[0]) : ''; + const borderStyle = parts[1] ? CSSStyleDeclarationValueParser.getBorderStyle(parts[1]) : ''; + const borderColor = parts[2] ? CSSStyleDeclarationValueParser.getColor(parts[2]) : ''; + + if (borderWidth && borderStyle !== null && borderColor !== null) { + this.properties['border-top-width'] = { name, important, value: borderWidth }; + this.properties['border-top-style'] = { name, important, value: borderStyle }; + this.properties['border-top-color'] = { name, important, value: borderColor }; + this.properties['border-right-width'] = { name, important, value: borderWidth }; + this.properties['border-right-style'] = { name, important, value: borderStyle }; + this.properties['border-right-color'] = { name, important, value: borderColor }; + this.properties['border-bottom-width'] = { name, important, value: borderWidth }; + this.properties['border-bottom-style'] = { name, important, value: borderStyle }; + this.properties['border-bottom-color'] = { name, important, value: borderColor }; + this.properties['border-left-width'] = { name, important, value: borderWidth }; + this.properties['border-left-style'] = { name, important, value: borderStyle }; + this.properties['border-left-color'] = { name, important, value: borderColor }; + } + } + + /** + * Sets border radius. + * + * @param name Name. + * @param value Value. + * @param important Important. + */ + private setBorderRadius(name: string, value: string, important: boolean): void { + const parts = value.split(/ +/); + const topLeft = parts[0] ? CSSStyleDeclarationValueParser.getMeasurement(parts[0]) : ''; + const topRight = parts[1] ? CSSStyleDeclarationValueParser.getMeasurement(parts[1]) : ''; + const bottomRight = parts[2] ? CSSStyleDeclarationValueParser.getMeasurement(parts[2]) : ''; + const bottomLeft = parts[3] ? CSSStyleDeclarationValueParser.getMeasurement(parts[3]) : ''; + + if (topLeft) { + this.properties['border-top-left-radius'] = { name, important, value: topLeft }; + } + if (topLeft && topRight) { + this.properties['border-top-right-radius'] = { name, important, value: topRight }; + } + if (topLeft && topRight && bottomRight) { + this.properties['border-bottom-right-radius'] = { name, important, value: bottomRight }; + } + if (topLeft && topRight && bottomRight && bottomLeft) { + this.properties['border-bottom-left-radius'] = { name, important, value: bottomLeft }; + } + } + + /** + * Sets border top, right, bottom or left. + * + * @param name Name. + * @param value Value. + * @param important Important. + */ + private setBorderPosition(name: string, value: string, important: boolean): void { + const parts = value.split(/ +/); + const borderWidth = parts[0] ? CSSStyleDeclarationValueParser.getBorderWidth(parts[0]) : ''; + const borderStyle = parts[1] ? CSSStyleDeclarationValueParser.getBorderStyle(parts[1]) : ''; + const borderColor = parts[2] ? CSSStyleDeclarationValueParser.getColor(parts[2]) : ''; + const borderName = name.split('-')[1]; + + if (borderWidth && borderStyle !== null && borderColor !== null) { + this.properties[`border-${borderName}-width`] = { name, important, value: borderWidth }; + this.properties[`border-${borderName}-style`] = { name, important, value: borderStyle }; + this.properties[`border-${borderName}-color`] = { name, important, value: borderColor }; + } + } + + /** + * Sets padding. + * + * @param name Name. + * @param value Value. + * @param important Important. + */ + private setPadding(name: string, value: string, important: boolean): void { + const parts = value.split(/ +/); + const top = parts[0] ? CSSStyleDeclarationValueParser.getMeasurement(parts[0]) : ''; + const right = parts[1] ? CSSStyleDeclarationValueParser.getMeasurement(parts[1]) : ''; + const bottom = parts[2] ? CSSStyleDeclarationValueParser.getMeasurement(parts[2]) : ''; + const left = parts[3] ? CSSStyleDeclarationValueParser.getMeasurement(parts[3]) : ''; + + if (top) { + this.properties['padding-top'] = { name, important, value: top }; + } + if (top && right) { + this.properties['padding-right'] = { name, important, value: right }; + } + if (top && right && bottom) { + this.properties['padding-bottom'] = { name, important, value: bottom }; + } + if (top && right && bottom && left) { + this.properties['padding-left'] = { name, important, value: left }; + } + } + + /** + * Sets margin. + * + * @param name Name. + * @param value Value. + * @param important Important. + */ + private setMargin(name: string, value: string, important: boolean): void { + const parts = value.split(/ +/); + const top = parts[0] ? CSSStyleDeclarationValueParser.getMargin(parts[0]) : ''; + const right = parts[1] ? CSSStyleDeclarationValueParser.getMargin(parts[1]) : ''; + const bottom = parts[2] ? CSSStyleDeclarationValueParser.getMargin(parts[2]) : ''; + const left = parts[3] ? CSSStyleDeclarationValueParser.getMargin(parts[3]) : ''; + + if (top) { + this.properties['margin-top'] = { name, important, value: top }; + } + if (top && right) { + this.properties['margin-right'] = { name, important, value: right }; + } + if (top && right && bottom) { + this.properties['margin-bottom'] = { name, important, value: bottom }; + } + if (top && right && bottom && left) { + this.properties['margin-left'] = { name, important, value: left }; + } + } + + /** + * Sets flex. + * + * @param name Name. + * @param value Value. + * @param important Important. + */ + private setFlex(name: string, value: string, important: boolean): void { + const lowerValue = value.trim().toLowerCase(); + + switch (lowerValue) { + case 'none': + this.properties['flex-grow'] = { name, important, value: '0' }; + this.properties['flex-shrink'] = { name, important, value: '0' }; + this.properties['flex-basis'] = { name, important, value: 'auto' }; + return; + case 'auto': + this.properties['flex-grow'] = { name, important, value: '1' }; + this.properties['flex-shrink'] = { name, important, value: '1' }; + this.properties['flex-basis'] = { name, important, value: 'auto' }; + return; + case 'initial': + this.properties['flex-grow'] = { name, important, value: '0' }; + this.properties['flex-shrink'] = { name, important, value: '0' }; + this.properties['flex-basis'] = { name, important, value: 'auto' }; + return; + case 'inherit': + this.properties['flex-grow'] = { name, important, value: 'inherit' }; + this.properties['flex-shrink'] = { name, important, value: 'inherit' }; + this.properties['flex-basis'] = { name, important, value: 'inherit' }; + return; + } + + const parts = value.split(/ +/); + const flexGrow = parts[0] ? CSSStyleDeclarationValueParser.getInteger(parts[0]) : ''; + const flexShrink = parts[1] ? CSSStyleDeclarationValueParser.getInteger(parts[1]) : ''; + const flexBasis = parts[2] ? CSSStyleDeclarationValueParser.getFlexBasis(parts[2]) : ''; + + if (flexGrow && flexShrink && flexBasis) { + this.properties['flex-grow'] = { name, important, value: flexGrow }; + this.properties['flex-shrink'] = { name, important, value: flexShrink }; + this.properties['flex-basis'] = { name, important, value: flexBasis }; + } + } + + /** + * Sets background. + * + * @param name Name. + * @param value Value. + * @param important Important. + */ + private setBackground(name: string, value: string, important: boolean): void { + const parts = value.split(/ +/); + + if (!parts[0]) { + return; + } + + // First value can be image if color is not specified. + if (!CSSStyleDeclarationValueParser.getColor(parts[0])) { + parts.unshift(''); + } + + const color = parts[0] ? CSSStyleDeclarationValueParser.getColor(parts[0]) : ''; + const image = parts[1] ? CSSStyleDeclarationValueParser.getURL(parts[1]) : ''; + const repeat = parts[2] ? CSSStyleDeclarationValueParser.getBackgroundRepeat(parts[2]) : ''; + const attachment = parts[3] + ? CSSStyleDeclarationValueParser.getBackgroundAttachment(parts[3]) + : ''; + const position = parts[4] ? CSSStyleDeclarationValueParser.getBackgroundPosition(parts[4]) : ''; + + if ((color || image) && repeat !== null && attachment !== null && position !== null) { + if (color) { + this.properties['background-color'] = { name, important, value: color }; + } + if (image) { + this.properties['background-image'] = { name, important, value: image }; + } + this.properties['background-repeat'] = { name, important, value: repeat }; + this.properties['background-attachment'] = { name, important, value: attachment }; + this.properties['background-position'] = { name, important, value: position }; + } + } + + /** + * Reads a string. + * + * @param styleString Style string (e.g. "border: 2px solid red; font-size: 12px;"). + */ + private fromString(styleString: string): { + [k: string]: ICSSStyleDeclarationProperty; + } {} +} diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyWriter.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyWriter.ts deleted file mode 100644 index f181c592a..000000000 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyWriter.ts +++ /dev/null @@ -1,415 +0,0 @@ -import ICSSStyleDeclarationProperty from '../ICSSStyleDeclarationProperty'; -import CSSStyleDeclarationValueParser from './CSSStyleDeclarationValueParser'; -import CSSStyleDeclarationPropertyWriterPropertyNames from './CSSStyleDeclarationPropertyWriterPropertyNames'; - -/** - * Computed style property parser. - */ -export default class CSSStyleDeclarationPropertyWriter { - private properties: { - [k: string]: ICSSStyleDeclarationProperty; - }; - - /** - * Class construtor. - * - * @param properties Properties. - */ - constructor(properties: { [k: string]: ICSSStyleDeclarationProperty }) { - this.properties = properties; - } - - /** - * Removes a property. - * - * @param name Property name. - */ - public remove(name: string): void { - if (CSSStyleDeclarationPropertyWriterPropertyNames[name]) { - for (const propertyName of CSSStyleDeclarationPropertyWriterPropertyNames[name]) { - delete this.properties[propertyName]; - } - } else { - delete this.properties[name]; - } - } - - /** - * Sets a property. - * - * @param property Property. - */ - public set(property: ICSSStyleDeclarationProperty): void { - const properties = this.getProperties(property); - for (const name of Object.keys(properties)) { - this.properties[name] = properties[name]; - } - } - - /** - * Returns properties to set. - * - * @param property Property. - * @returns Properties. - */ - private getProperties(property: ICSSStyleDeclarationProperty): { - [k: string]: ICSSStyleDeclarationProperty; - } { - const { name, important } = property; - let { value } = property; - - switch (name) { - case 'border': - return this.getBorderProperties(property); - case 'border-left': - case 'border-bottom': - case 'border-right': - case 'border-top': - return this.getBorderPositionProperties(property); - case 'border-width': - value = CSSStyleDeclarationValueParser.getBorderWidth(value); - return value - ? { - 'border-top-width': { name, important, value }, - 'border-right-width': { name, important, value }, - 'border-bottom-width': { name, important, value }, - 'border-left-width': { name, important, value } - } - : {}; - case 'border-style': - value = CSSStyleDeclarationValueParser.getBorderStyle(value); - return value - ? { - 'border-top-style': { name, important, value }, - 'border-right-style': { name, important, value }, - 'border-bottom-style': { name, important, value }, - 'border-left-style': { name, important, value } - } - : {}; - case 'border-color': - value = CSSStyleDeclarationValueParser.getBorderCollapse(value); - return value - ? { - 'border-top-color': { name, important, value }, - 'border-right-color': { name, important, value }, - 'border-bottom-color': { name, important, value }, - 'border-left-color': { name, important, value } - } - : {}; - case 'border-radius': - return this.getBorderRadiusProperties(property); - case 'border-collapse': - value = CSSStyleDeclarationValueParser.getBorderCollapse(value); - return value ? { [name]: { name, important, value } } : {}; - case 'clear': - value = CSSStyleDeclarationValueParser.getClear(value); - return value ? { [name]: { name, important, value } } : {}; - case 'clip': - value = CSSStyleDeclarationValueParser.getClip(value); - return value ? { [name]: { name, important, value } } : {}; - case 'css-float': - case 'float': - value = CSSStyleDeclarationValueParser.getFloat(value); - return value ? { [name]: { name, important, value } } : {}; - case 'flex': - return this.getFlexProperties(property); - case 'flex-shrink': - case 'flex-grow': - value = CSSStyleDeclarationValueParser.getInteger(value); - return value ? { [name]: { name, important, value } } : {}; - case 'flex-basis': - value = CSSStyleDeclarationValueParser.getFlexBasis(value); - return value ? { [name]: { name, important, value } } : {}; - case 'padding': - return this.getPaddingProperties(property); - case 'margin': - return this.getMarginProperties(property); - case 'background': - return this.getBackgroundProperties(property); - case 'top': - case 'right': - case 'bottom': - case 'left': - value = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); - return value ? { [name]: { name, important, value } } : {}; - case 'padding-top': - case 'padding-bottom': - case 'padding-left': - case 'padding-right': - value = CSSStyleDeclarationValueParser.getMeasurement(value); - return value ? { [name]: { name, important, value } } : {}; - case 'margin-top': - case 'margin-bottom': - case 'margin-left': - case 'margin-right': - value = CSSStyleDeclarationValueParser.getMargin(value); - return value ? { [name]: { name, important, value } } : {}; - case 'border-top-width': - case 'border-bottom-width': - case 'border-left-width': - case 'border-right-width': - value = CSSStyleDeclarationValueParser.getBorderWidth(value); - return value ? { [name]: { name, important, value } } : {}; - case 'font-size': - value = CSSStyleDeclarationValueParser.getFontSize(value); - return value ? { [name]: { name, important, value } } : {}; - case 'color': - case 'flood-color': - case 'border-top-color': - case 'border-bottom-color': - case 'border-left-color': - case 'border-right-color': - value = CSSStyleDeclarationValueParser.getColor(value); - return value ? { [name]: { name, important, value } } : {}; - case 'border-top-style': - case 'border-bottom-style': - case 'border-left-style': - case 'border-right-style': - value = CSSStyleDeclarationValueParser.getBorderStyle(value); - return value ? { [name]: { name, important, value } } : {}; - } - - return { - [name]: { name, important, value } - }; - } - - /** - * Returns border properties. - * - * @param property Property. - * @returns Properties. - */ - private getBorderProperties(property: ICSSStyleDeclarationProperty): { - [k: string]: ICSSStyleDeclarationProperty; - } { - const { name, value, important } = property; - const parts = value.split(/ +/); - - parts[0] = parts[0] ? CSSStyleDeclarationValueParser.getBorderWidth(parts[0]) : ''; - parts[1] = parts[1] ? CSSStyleDeclarationValueParser.getBorderStyle(parts[1]) : ''; - parts[2] = parts[2] ? CSSStyleDeclarationValueParser.getColor(parts[2]) : ''; - - if (!parts[0] || parts[1] === null || parts[2] === null) { - return {}; - } - - return { - 'border-top-width': { name, important, value: parts[0] }, - 'border-right-width': { name, important, value: parts[0] }, - 'border-bottom-width': { name, important, value: parts[0] }, - 'border-left-width': { name, important, value: parts[0] }, - 'border-top-style': { name, important, value: parts[1] }, - 'border-right-style': { name, important, value: parts[1] }, - 'border-bottom-style': { name, important, value: parts[1] }, - 'border-left-style': { name, important, value: parts[1] }, - 'border-top-color': { name, important, value: parts[2] }, - 'border-right-color': { name, important, value: parts[2] }, - 'border-bottom-color': { name, important, value: parts[2] }, - 'border-left-color': { name, important, value: parts[2] } - }; - } - - /** - * Returns border radius properties. - * - * @param property Property. - * @returns Properties. - */ - private getBorderRadiusProperties(property: ICSSStyleDeclarationProperty): { - [k: string]: ICSSStyleDeclarationProperty; - } { - const { name, value, important } = property; - const parts = value.split(/ +/); - parts[0] = parts[0] ? CSSStyleDeclarationValueParser.getMeasurement(parts[0]) : ''; - parts[1] = parts[1] ? CSSStyleDeclarationValueParser.getMeasurement(parts[1]) : ''; - parts[2] = parts[2] ? CSSStyleDeclarationValueParser.getMeasurement(parts[2]) : ''; - parts[3] = parts[3] ? CSSStyleDeclarationValueParser.getMeasurement(parts[3]) : ''; - if (!parts[0] || parts[1] === null || parts[2] === null || parts[3] === null) { - return {}; - } - return { - 'border-top-left-radius': { name, important, value: parts[0] }, - 'border-top-right-radius': { name, important, value: parts[1] }, - 'border-bottom-right-radius': { name, important, value: parts[2] }, - 'border-bottom-left-radius': { name, important, value: parts[3] } - }; - } - - /** - * Returns border position properties. - * - * @param property Property. - * @returns Properties. - */ - private getBorderPositionProperties(property: ICSSStyleDeclarationProperty): { - [k: string]: ICSSStyleDeclarationProperty; - } { - const { name, value, important } = property; - const parts = value.split(/ +/); - parts[0] = parts[0] ? CSSStyleDeclarationValueParser.getBorderWidth(parts[0]) : ''; - parts[1] = parts[1] ? CSSStyleDeclarationValueParser.getBorderStyle(parts[1]) : ''; - parts[2] = parts[2] ? CSSStyleDeclarationValueParser.getColor(parts[2]) : ''; - - if (!parts[0] || parts[1] === null || parts[2] === null) { - return {}; - } - - const borderName = name.split('-')[1]; - - return { - [`border-${borderName}-width`]: { name, important, value: parts[0] }, - [`border-${borderName}-style`]: { name, important, value: parts[1] }, - [`border-${borderName}-color`]: { name, important, value: parts[2] } - }; - } - - /** - * Returns padding properties. - * - * @param property Property. - * @returns Properties. - */ - private getPaddingProperties(property: ICSSStyleDeclarationProperty): { - [k: string]: ICSSStyleDeclarationProperty; - } { - const { name, value, important } = property; - const parts = value.split(/ +/); - parts[0] = parts[0] ? CSSStyleDeclarationValueParser.getMeasurement(parts[0]) : ''; - parts[1] = parts[1] ? CSSStyleDeclarationValueParser.getMeasurement(parts[1]) : ''; - parts[2] = parts[2] ? CSSStyleDeclarationValueParser.getMeasurement(parts[2]) : ''; - parts[3] = parts[3] ? CSSStyleDeclarationValueParser.getMeasurement(parts[3]) : ''; - - if (!parts[0] || parts[1] === null || parts[2] === null || parts[3] === null) { - return {}; - } - - return { - ['padding-top']: { name, important, value: parts[0] }, - ['padding-right']: { name, important, value: parts[1] }, - ['padding-bottom']: { name, important, value: parts[2] }, - ['padding-left']: { name, important, value: parts[3] } - }; - } - - /** - * Returns margin properties. - * - * @param property Property. - * @returns Properties. - */ - private getMarginProperties(property: ICSSStyleDeclarationProperty): { - [k: string]: ICSSStyleDeclarationProperty; - } { - const { name, value, important } = property; - const parts = value.split(/ +/); - parts[0] = parts[0] ? CSSStyleDeclarationValueParser.getMeasurement(parts[0]) : ''; - parts[1] = parts[1] ? CSSStyleDeclarationValueParser.getMeasurement(parts[1]) : ''; - parts[2] = parts[2] ? CSSStyleDeclarationValueParser.getMeasurement(parts[2]) : ''; - parts[3] = parts[3] ? CSSStyleDeclarationValueParser.getMeasurement(parts[3]) : ''; - - if (!parts[0] || parts[1] === null || parts[2] === null || parts[3] === null) { - return {}; - } - - return { - ['padding-top']: { name, important, value: parts[0] }, - ['padding-right']: { name, important, value: parts[1] }, - ['padding-bottom']: { name, important, value: parts[2] }, - ['padding-left']: { name, important, value: parts[3] } - }; - } - - /** - * Returns flex properties. - * - * @param property Property. - * @returns Properties. - */ - private getFlexProperties(property: ICSSStyleDeclarationProperty): { - [k: string]: ICSSStyleDeclarationProperty; - } { - const { name, value, important } = property; - const lowerValue = value.trim().toLowerCase(); - switch (lowerValue) { - case 'none': - return { - 'flex-grow': { name, important, value: '0' }, - 'flex-shrink': { name, important, value: '0' }, - 'flex-basis': { name, important, value: 'auto' } - }; - case 'auto': - return { - 'flex-grow': { name, important, value: '1' }, - 'flex-shrink': { name, important, value: '1' }, - 'flex-basis': { name, important, value: 'auto' } - }; - case 'initial': - return { - 'flex-grow': { name, important, value: '0' }, - 'flex-shrink': { name, important, value: '1' }, - 'flex-basis': { name, important, value: 'auto' } - }; - case 'inherit': - return { - 'flex-grow': { name, important, value: 'inherit' }, - 'flex-shrink': { name, important, value: 'inherit' }, - 'flex-basis': { name, important, value: 'inherit' } - }; - } - - const parts = value.split(/ +/); - parts[0] = parts[0] ? CSSStyleDeclarationValueParser.getInteger(parts[0]) : ''; - parts[1] = parts[1] ? CSSStyleDeclarationValueParser.getInteger(parts[1]) : ''; - parts[2] = parts[2] ? CSSStyleDeclarationValueParser.getFlexBasis(parts[2]) : ''; - - if (!parts[0] || !parts[1] || !parts[2]) { - return {}; - } - - return { - 'flex-grow': { name, important, value: parts[0] }, - 'flex-shrink': { name, important, value: parts[1] }, - 'flex-basis': { name, important, value: parts[2] } - }; - } - - /** - * Returns background properties. - * - * @param property Property. - * @returns Properties. - */ - private getBackgroundProperties(property: ICSSStyleDeclarationProperty): { - [k: string]: ICSSStyleDeclarationProperty; - } { - const { name, value, important } = property; - const parts = value.split(/ +/); - - if (!parts[0]) { - return {}; - } - - // First value can be color or image url - if (!CSSStyleDeclarationValueParser.getColor(parts[0])) { - parts.unshift(''); - } - - parts[0] = parts[0] ? CSSStyleDeclarationValueParser.getColor(parts[0]) : ''; - parts[1] = parts[1] ? CSSStyleDeclarationValueParser.getURL(parts[1]) : ''; - parts[2] = parts[2] ? CSSStyleDeclarationValueParser.getBackgroundRepeat(parts[2]) : ''; - parts[3] = parts[3] ? CSSStyleDeclarationValueParser.getBackgroundAttachment(parts[3]) : ''; - parts[4] = parts[4] ? CSSStyleDeclarationValueParser.getBackgroundPosition(parts[4]) : ''; - if ((!parts[0] && !parts[1]) || parts[2] === null || parts[3] === null || parts[4] === null) { - return {}; - } - - return { - 'background-color': { name, important, value: parts[0] }, - 'background-image': { name, important, value: parts[1] }, - 'background-repeat': { name, important, value: parts[2] }, - 'background-attachment': { name, important, value: parts[3] }, - 'background-position': { name, important, value: parts[4] } - }; - } -} diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStyleString.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStyleString.ts index 8ddf700c2..5872a1014 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStyleString.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStyleString.ts @@ -1,6 +1,6 @@ import IElement from '../../../nodes/element/IElement'; import ICSSStyleDeclarationProperty from '../ICSSStyleDeclarationProperty'; -import CSSStyleDeclarationPropertyWriter from './CSSStyleDeclarationPropertyWriter'; +import CSSStyleDeclarationPropertyManager from './CSSStyleDeclarationPropertyManager'; /** * CSS Style Declaration utility @@ -17,7 +17,7 @@ export default class CSSStyleDeclarationStyleString { } { const style = {}; const parts = styleString.split(';'); - const writer = new CSSStyleDeclarationPropertyWriter(style); + const writer = new CSSStyleDeclarationPropertyManager(style); for (const part of parts) { if (part) { From 3d664f791f9fba2303b9c443a41012a83431e497 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Fri, 12 Aug 2022 15:31:49 +0200 Subject: [PATCH 16/84] #344@trivial: Continues on CSSStyleDeclaration. --- .../AbstractCSSStyleDeclaration.ts | 2 +- .../ICSSStyleDeclarationProperty.ts | 5 - .../CSSStyleDeclarationPropertyManager.ts | 225 +----- .../CSSStyleDeclarationPropertyReader.ts | 2 +- .../CSSStyleDeclarationPropertyValueParser.ts | 742 ++++++++++++++++++ .../CSSStyleDeclarationStyleString.ts | 2 +- .../CSSStyleDeclarationValueParser.ts | 420 ---------- .../ICSSStyleDeclarationPropertyValue.ts | 4 + 8 files changed, 752 insertions(+), 650 deletions(-) delete mode 100644 packages/happy-dom/src/css/declaration/ICSSStyleDeclarationProperty.ts create mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValueParser.ts delete mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts create mode 100644 packages/happy-dom/src/css/declaration/utilities/ICSSStyleDeclarationPropertyValue.ts diff --git a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts index e5241f65e..264b4aefb 100644 --- a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts +++ b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts @@ -5,7 +5,7 @@ import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum'; import DOMException from '../../exception/DOMException'; import CSSStyleDeclarationStyleString from './utilities/CSSStyleDeclarationStyleString'; import CSSStyleDeclarationPropertyManager from './utilities/CSSStyleDeclarationPropertyManager'; -import ICSSStyleDeclarationProperty from './ICSSStyleDeclarationProperty'; +import ICSSStyleDeclarationProperty from './utilities/ICSSStyleDeclarationPropertyValue'; import CSSStyleDeclarationPropertyReader from './utilities/CSSStyleDeclarationPropertyReader'; /** diff --git a/packages/happy-dom/src/css/declaration/ICSSStyleDeclarationProperty.ts b/packages/happy-dom/src/css/declaration/ICSSStyleDeclarationProperty.ts deleted file mode 100644 index cd9da3719..000000000 --- a/packages/happy-dom/src/css/declaration/ICSSStyleDeclarationProperty.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default interface ICSSStyleDeclarationProperty { - name: string; - value: string; - important: boolean; -} diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts index f4a32b832..67b3fd4a7 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts @@ -1,5 +1,5 @@ -import ICSSStyleDeclarationProperty from '../ICSSStyleDeclarationProperty'; -import CSSStyleDeclarationValueParser from './CSSStyleDeclarationValueParser'; +import ICSSStyleDeclarationPropertyValue from './ICSSStyleDeclarationPropertyValue'; +import CSSStyleDeclarationValueParser from './CSSStyleDeclarationPropertyValueParser'; import CSSStyleDeclarationPropertyManagerPropertyNames from './CSSStyleDeclarationPropertyManagerPropertyNames'; /** @@ -7,7 +7,7 @@ import CSSStyleDeclarationPropertyManagerPropertyNames from './CSSStyleDeclarati */ export default class CSSStyleDeclarationPropertyManager { private properties: { - [k: string]: ICSSStyleDeclarationProperty; + [k: string]: ICSSStyleDeclarationPropertyValue; } = {}; /** @@ -329,225 +329,6 @@ export default class CSSStyleDeclarationPropertyManager { } } - /** - * Sets border. - * - * @param name Name. - * @param value Value. - * @param important Important. - */ - private setBorder(name: string, value: string, important: boolean): void { - const parts = value.split(/ +/); - const borderWidth = parts[0] ? CSSStyleDeclarationValueParser.getBorderWidth(parts[0]) : ''; - const borderStyle = parts[1] ? CSSStyleDeclarationValueParser.getBorderStyle(parts[1]) : ''; - const borderColor = parts[2] ? CSSStyleDeclarationValueParser.getColor(parts[2]) : ''; - - if (borderWidth && borderStyle !== null && borderColor !== null) { - this.properties['border-top-width'] = { name, important, value: borderWidth }; - this.properties['border-top-style'] = { name, important, value: borderStyle }; - this.properties['border-top-color'] = { name, important, value: borderColor }; - this.properties['border-right-width'] = { name, important, value: borderWidth }; - this.properties['border-right-style'] = { name, important, value: borderStyle }; - this.properties['border-right-color'] = { name, important, value: borderColor }; - this.properties['border-bottom-width'] = { name, important, value: borderWidth }; - this.properties['border-bottom-style'] = { name, important, value: borderStyle }; - this.properties['border-bottom-color'] = { name, important, value: borderColor }; - this.properties['border-left-width'] = { name, important, value: borderWidth }; - this.properties['border-left-style'] = { name, important, value: borderStyle }; - this.properties['border-left-color'] = { name, important, value: borderColor }; - } - } - - /** - * Sets border radius. - * - * @param name Name. - * @param value Value. - * @param important Important. - */ - private setBorderRadius(name: string, value: string, important: boolean): void { - const parts = value.split(/ +/); - const topLeft = parts[0] ? CSSStyleDeclarationValueParser.getMeasurement(parts[0]) : ''; - const topRight = parts[1] ? CSSStyleDeclarationValueParser.getMeasurement(parts[1]) : ''; - const bottomRight = parts[2] ? CSSStyleDeclarationValueParser.getMeasurement(parts[2]) : ''; - const bottomLeft = parts[3] ? CSSStyleDeclarationValueParser.getMeasurement(parts[3]) : ''; - - if (topLeft) { - this.properties['border-top-left-radius'] = { name, important, value: topLeft }; - } - if (topLeft && topRight) { - this.properties['border-top-right-radius'] = { name, important, value: topRight }; - } - if (topLeft && topRight && bottomRight) { - this.properties['border-bottom-right-radius'] = { name, important, value: bottomRight }; - } - if (topLeft && topRight && bottomRight && bottomLeft) { - this.properties['border-bottom-left-radius'] = { name, important, value: bottomLeft }; - } - } - - /** - * Sets border top, right, bottom or left. - * - * @param name Name. - * @param value Value. - * @param important Important. - */ - private setBorderPosition(name: string, value: string, important: boolean): void { - const parts = value.split(/ +/); - const borderWidth = parts[0] ? CSSStyleDeclarationValueParser.getBorderWidth(parts[0]) : ''; - const borderStyle = parts[1] ? CSSStyleDeclarationValueParser.getBorderStyle(parts[1]) : ''; - const borderColor = parts[2] ? CSSStyleDeclarationValueParser.getColor(parts[2]) : ''; - const borderName = name.split('-')[1]; - - if (borderWidth && borderStyle !== null && borderColor !== null) { - this.properties[`border-${borderName}-width`] = { name, important, value: borderWidth }; - this.properties[`border-${borderName}-style`] = { name, important, value: borderStyle }; - this.properties[`border-${borderName}-color`] = { name, important, value: borderColor }; - } - } - - /** - * Sets padding. - * - * @param name Name. - * @param value Value. - * @param important Important. - */ - private setPadding(name: string, value: string, important: boolean): void { - const parts = value.split(/ +/); - const top = parts[0] ? CSSStyleDeclarationValueParser.getMeasurement(parts[0]) : ''; - const right = parts[1] ? CSSStyleDeclarationValueParser.getMeasurement(parts[1]) : ''; - const bottom = parts[2] ? CSSStyleDeclarationValueParser.getMeasurement(parts[2]) : ''; - const left = parts[3] ? CSSStyleDeclarationValueParser.getMeasurement(parts[3]) : ''; - - if (top) { - this.properties['padding-top'] = { name, important, value: top }; - } - if (top && right) { - this.properties['padding-right'] = { name, important, value: right }; - } - if (top && right && bottom) { - this.properties['padding-bottom'] = { name, important, value: bottom }; - } - if (top && right && bottom && left) { - this.properties['padding-left'] = { name, important, value: left }; - } - } - - /** - * Sets margin. - * - * @param name Name. - * @param value Value. - * @param important Important. - */ - private setMargin(name: string, value: string, important: boolean): void { - const parts = value.split(/ +/); - const top = parts[0] ? CSSStyleDeclarationValueParser.getMargin(parts[0]) : ''; - const right = parts[1] ? CSSStyleDeclarationValueParser.getMargin(parts[1]) : ''; - const bottom = parts[2] ? CSSStyleDeclarationValueParser.getMargin(parts[2]) : ''; - const left = parts[3] ? CSSStyleDeclarationValueParser.getMargin(parts[3]) : ''; - - if (top) { - this.properties['margin-top'] = { name, important, value: top }; - } - if (top && right) { - this.properties['margin-right'] = { name, important, value: right }; - } - if (top && right && bottom) { - this.properties['margin-bottom'] = { name, important, value: bottom }; - } - if (top && right && bottom && left) { - this.properties['margin-left'] = { name, important, value: left }; - } - } - - /** - * Sets flex. - * - * @param name Name. - * @param value Value. - * @param important Important. - */ - private setFlex(name: string, value: string, important: boolean): void { - const lowerValue = value.trim().toLowerCase(); - - switch (lowerValue) { - case 'none': - this.properties['flex-grow'] = { name, important, value: '0' }; - this.properties['flex-shrink'] = { name, important, value: '0' }; - this.properties['flex-basis'] = { name, important, value: 'auto' }; - return; - case 'auto': - this.properties['flex-grow'] = { name, important, value: '1' }; - this.properties['flex-shrink'] = { name, important, value: '1' }; - this.properties['flex-basis'] = { name, important, value: 'auto' }; - return; - case 'initial': - this.properties['flex-grow'] = { name, important, value: '0' }; - this.properties['flex-shrink'] = { name, important, value: '0' }; - this.properties['flex-basis'] = { name, important, value: 'auto' }; - return; - case 'inherit': - this.properties['flex-grow'] = { name, important, value: 'inherit' }; - this.properties['flex-shrink'] = { name, important, value: 'inherit' }; - this.properties['flex-basis'] = { name, important, value: 'inherit' }; - return; - } - - const parts = value.split(/ +/); - const flexGrow = parts[0] ? CSSStyleDeclarationValueParser.getInteger(parts[0]) : ''; - const flexShrink = parts[1] ? CSSStyleDeclarationValueParser.getInteger(parts[1]) : ''; - const flexBasis = parts[2] ? CSSStyleDeclarationValueParser.getFlexBasis(parts[2]) : ''; - - if (flexGrow && flexShrink && flexBasis) { - this.properties['flex-grow'] = { name, important, value: flexGrow }; - this.properties['flex-shrink'] = { name, important, value: flexShrink }; - this.properties['flex-basis'] = { name, important, value: flexBasis }; - } - } - - /** - * Sets background. - * - * @param name Name. - * @param value Value. - * @param important Important. - */ - private setBackground(name: string, value: string, important: boolean): void { - const parts = value.split(/ +/); - - if (!parts[0]) { - return; - } - - // First value can be image if color is not specified. - if (!CSSStyleDeclarationValueParser.getColor(parts[0])) { - parts.unshift(''); - } - - const color = parts[0] ? CSSStyleDeclarationValueParser.getColor(parts[0]) : ''; - const image = parts[1] ? CSSStyleDeclarationValueParser.getURL(parts[1]) : ''; - const repeat = parts[2] ? CSSStyleDeclarationValueParser.getBackgroundRepeat(parts[2]) : ''; - const attachment = parts[3] - ? CSSStyleDeclarationValueParser.getBackgroundAttachment(parts[3]) - : ''; - const position = parts[4] ? CSSStyleDeclarationValueParser.getBackgroundPosition(parts[4]) : ''; - - if ((color || image) && repeat !== null && attachment !== null && position !== null) { - if (color) { - this.properties['background-color'] = { name, important, value: color }; - } - if (image) { - this.properties['background-image'] = { name, important, value: image }; - } - this.properties['background-repeat'] = { name, important, value: repeat }; - this.properties['background-attachment'] = { name, important, value: attachment }; - this.properties['background-position'] = { name, important, value: position }; - } - } - /** * Reads a string. * diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyReader.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyReader.ts index 82a40539b..9e2128339 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyReader.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyReader.ts @@ -1,4 +1,4 @@ -import ICSSStyleDeclarationProperty from '../ICSSStyleDeclarationProperty'; +import ICSSStyleDeclarationProperty from './ICSSStyleDeclarationPropertyValue'; /** * Computed style property parser. */ diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValueParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValueParser.ts new file mode 100644 index 000000000..7764f37ce --- /dev/null +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValueParser.ts @@ -0,0 +1,742 @@ +import ICSSStyleDeclarationPropertyValue from './ICSSStyleDeclarationPropertyValue'; + +const COLOR_REGEXP = + /^#([0-9a-fA-F]{3,4}){1,2}$|^rgb\(([^)]*)\)$|^rgba\(([^)]*)\)$|^hsla?\(\s*(-?\d+|-?\d*.\d+)\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)%\s*(,\s*(-?\d+|-?\d*.\d+)\s*)?\)/; + +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 URL_REGEXP = /^url\(\s*([^)]*)\s*\)$/; +const RECT_REGEXP = /^rect\((.*)\)$/i; +const BORDER_STYLE = [ + 'none', + 'hidden', + 'dotted', + 'dashed', + 'solid', + 'double', + 'groove', + 'ridge', + '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 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 FONT_SIZE = [ + 'xx-small', + 'x-small', + 'small', + 'medium', + 'large', + 'x-large', + 'xx-large', + 'xxx-large', + 'smaller', + 'larger', + 'inherit', + 'initial', + 'revert', + 'unset' +]; + +/** + * Computed style property parser. + */ +export default class CSSStyleDeclarationPropertyValueParser { + /** + * Returns length. + * + * @param value Value. + * @returns Parsed value. + */ + public static getLength(value: string): string { + if (value === '0') { + return '0px'; + } + if (LENGTH_REGEXP.test(value)) { + return value; + } + return null; + } + + /** + * Returns percentance. + * + * @param value Value. + * @returns Parsed value. + */ + public static getPercentage(value: string): string { + if (value === '0') { + return '0%'; + } + if (PERCENTAGE_REGEXP.test(value)) { + return value; + } + return null; + } + + /** + * Returns measurement. + * + * @param value Value. + * @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); + } + + /** + * Returns measurement or auto. + * + * @param value Value. + * @returns Parsed value. + */ + public static getMeasurementOrAuto(value: string): string { + const lowerValue = value.toLowerCase(); + if (lowerValue === 'auto') { + return lowerValue; + } + return this.getMeasurement(value); + } + + /** + * Returns integer. + * + * @param value Value. + * @returns Parsed value. + */ + public static getInteger(value: string): string { + if (!isNaN(parseInt(value))) { + return value; + } + return null; + } + + /** + * Returns color. + * + * @param value Value. + * @returns Parsed value. + */ + public static getColor(value: string): string { + if (COLOR_REGEXP.test(value)) { + return value; + } + return null; + } + + /** + * Returns URL. + * + * Based on: + * https://github.com/jsdom/cssstyle/blob/master/lib/parsers.js#L222 + * + * @param value Value. + * @returns Parsed value. + */ + public static getURL(value: string): string { + if (!value) { + return null; + } + + const lowerValue = value.toLowerCase(); + + if (lowerValue === 'none' || lowerValue === 'inherit') { + return lowerValue; + } + + const result = URL_REGEXP.exec(value); + + if (!result) { + return null; + } + + let url = result[1]; + + if ((url[0] === '"' || url[0] === "'") && url[0] !== url[url.length - 1]) { + return null; + } + + if (url[0] === '"' || url[0] === "'") { + url = url.substring(1, url.length - 1); + } + + for (let i = 0; i < url.length; i++) { + switch (url[i]) { + case '(': + case ')': + case ' ': + case '\t': + case '\n': + case "'": + case '"': + return null; + case '\\': + i++; + break; + } + } + + return value; + } + + /** + * Returns border style. + * + * @param value Value. + * @returns Parsed value. + */ + public static getBorderStyle(value: string): string { + const lowerValue = value.toLowerCase(); + if (BORDER_STYLE.includes(lowerValue)) { + return lowerValue; + } + return null; + } + + /** + * Returns border collapse. + * + * @param value Value. + * @returns Parsed value. + */ + public static getBorderCollapse(value: string): string { + const lowerValue = value.toLowerCase(); + if (BORDER_COLLAPSE.includes(lowerValue)) { + return lowerValue; + } + return null; + } + + /** + * Returns background repeat. + * + * @param value Value. + * @returns Parsed value. + */ + public static getBackgroundRepeat(value: string): string { + const lowerValue = value.toLowerCase(); + if (BACKGROUND_REPEAT.includes(lowerValue)) { + return lowerValue; + } + return null; + } + + /** + * Returns background attachment. + * + * @param value Value. + * @returns Parsed value. + */ + public static getBackgroundAttachment(value: string): string { + const lowerValue = value.toLowerCase(); + if (BACKGROUND_ATTACHMENT.includes(lowerValue)) { + return lowerValue; + } + return null; + } + + /** + * Returns URL. + * + * Based on: + * https://github.com/jsdom/cssstyle/blob/master/lib/properties/backgroundPosition.js + * + * @param value Value. + * @returns Parsed value. + */ + public static getBackgroundPosition(value: string): string { + if (!value) { + return null; + } + const parts = value.split(/\s+/); + if (parts.length > 2 || parts.length < 1) { + return null; + } + if (parts.length === 1) { + if (this.getLength(parts[0])) { + return value; + } + if (parts[0]) { + const lowerValue = value.toLowerCase(); + if (BACKGROUND_POSITION.includes(lowerValue) || lowerValue === 'inherit') { + return lowerValue; + } + } + return null; + } + if ( + (this.getLength(parts[0]) || this.getPercentage(parts[0])) && + (this.getLength(parts[1]) || this.getPercentage(parts[1])) + ) { + return value.toLowerCase(); + } + if ( + BACKGROUND_POSITION.includes(parts[0].toLowerCase()) && + BACKGROUND_POSITION.includes(parts[1].toLowerCase()) + ) { + return `${parts[0].toLowerCase()} ${parts[1].toLowerCase()}`; + } + return null; + } + + /** + * Returns flex basis. + * + * @param value Value. + * @returns Parsed value. + */ + public static getFlexBasis(value: string): string { + const lowerValue = value.toLowerCase(); + if (FLEX_BASIS.includes(lowerValue)) { + return lowerValue; + } + return this.getLength(value); + } + + /** + * Returns margin. + * + * @param value Value. + * @returns Parsed value. + */ + public static getMarginValue(value: string): string { + const lowerValue = value.toLowerCase(); + if (lowerValue === 'auto') { + return lowerValue; + } + return this.getMeasurement(value); + } + + /** + * Returns border width. + * + * @param value Value. + * @returns Parsed value. + */ + public static getBorderWidth(value: string): string { + const lowerValue = value.toLowerCase(); + if (BORDER_WIDTH.includes(lowerValue)) { + return lowerValue; + } + return this.getLength(value); + } + + /** + * Returns clear. + * + * @param value Value. + * @returns Parsed value. + */ + public static getClear(value: string): string { + const lowerValue = value.toLowerCase(); + if (CLEAR.includes(lowerValue)) { + return lowerValue; + } + return null; + } + + /** + * Returns clip + * + * Based on: + * https://github.com/jsdom/cssstyle/blob/master/lib/properties/clip.js + * + * @param value Value. + * @returns Parsed value. + */ + public static getClip(value: string): string { + const lowerValue = value.toLowerCase(); + if (lowerValue === 'auto' || lowerValue === 'initial' || lowerValue === 'inherit') { + return lowerValue; + } + const matches = lowerValue.match(RECT_REGEXP); + if (!matches) { + return null; + } + const parts = matches[1].split(/\s*,\s*/); + if (parts.length !== 4) { + return null; + } + for (const part of parts) { + if (!this.getMeasurement(part)) { + return null; + } + } + return value; + } + + /** + * Returns float. + * + * @param value Value. + * @returns Parsed value. + */ + public static getFloat(value: string): string { + const lowerValue = value.toLowerCase(); + if (FLOAT.includes(lowerValue)) { + return lowerValue; + } + return null; + } + + /** + * Returns font size. + * + * @param value Value. + * @returns Parsed value. + */ + public static getFontSize(value: string): string { + const lowerValue = value.toLowerCase(); + if (FONT_SIZE.includes(lowerValue)) { + return lowerValue; + } + return this.getLength(value) || this.getPercentage(value); + } + + /** + * Returns border. + * + * @param value Value. + * @param important Important. + * @returns Property values. + */ + public static getBorder( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const parts = value.split(/ +/); + const borderWidth = parts[0] ? this.getBorderWidth(parts[0]) : ''; + const borderStyle = parts[1] ? this.getBorderStyle(parts[1]) : ''; + const borderColor = parts[2] ? this.getColor(parts[2]) : ''; + + if (borderWidth && borderStyle !== null && borderColor !== null) { + return { + ['border-top-width']: { important, value: borderWidth }, + ['border-top-style']: { important, value: borderStyle }, + ['border-top-color']: { important, value: borderColor }, + ['border-right-width']: { important, value: borderWidth }, + ['border-right-style']: { important, value: borderStyle }, + ['border-right-color']: { important, value: borderColor }, + ['border-bottom-width']: { important, value: borderWidth }, + ['border-bottom-style']: { important, value: borderStyle }, + ['border-bottom-color']: { important, value: borderColor }, + ['border-left-width']: { important, value: borderWidth }, + ['border-left-style']: { important, value: borderStyle }, + ['border-left-color']: { important, value: borderColor } + }; + } + + return null; + } + + /** + * Returns border radius. + * + * @param value Value. + * @param important Important. + * @returns Property values. + */ + public static getBorderRadius( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const parts = value.split(/ +/); + const topLeft = parts[0] ? this.getMeasurement(parts[0]) : ''; + const topRight = parts[1] ? this.getMeasurement(parts[1]) : ''; + const bottomRight = parts[2] ? this.getMeasurement(parts[2]) : ''; + const bottomLeft = parts[3] ? this.getMeasurement(parts[3]) : ''; + const propertyValues = {}; + + if (topLeft) { + propertyValues['border-top-left-radius'] = { important, value: topLeft }; + } + if (topLeft && topRight) { + propertyValues['border-top-right-radius'] = { important, value: topRight }; + } + if (topLeft && topRight && bottomRight) { + propertyValues['border-bottom-right-radius'] = { important, value: bottomRight }; + } + if (topLeft && topRight && bottomRight && bottomLeft) { + propertyValues['border-bottom-left-radius'] = { important, value: bottomLeft }; + } + + return topLeft ? propertyValues : null; + } + + /** + * Returns border top, right, bottom or left. + * + * @param value Value. + * @param important Important. + * @returns Property values. + */ + public static getBorderTop( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + return this.getBorderPosition('border-top', value, important); + } + + /** + * Returns border top, right, bottom or left. + * + * @param value Value. + * @param important Important. + * @returns Property values. + */ + public static getBorderRight( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + return this.getBorderPosition('border-top', value, important); + } + + /** + * Returns border top, right, bottom or left. + * + * @param value Value. + * @param important Important. + * @returns Property values. + */ + public static getBorderBottom( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + return this.getBorderPosition('border-top', value, important); + } + + /** + * Returns border top, right, bottom or left. + * + * @param value Value. + * @param important Important. + * @returns Property values. + */ + public static getBorderLeft( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + return this.getBorderPosition('border-top', value, important); + } + + /** + * Returns padding. + * + * @param value Value. + * @param important Important. + */ + public static getPadding( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const parts = value.split(/ +/); + const top = parts[0] ? this.getMeasurement(parts[0]) : ''; + const right = parts[1] ? this.getMeasurement(parts[1]) : ''; + const bottom = parts[2] ? this.getMeasurement(parts[2]) : ''; + const left = parts[3] ? this.getMeasurement(parts[3]) : ''; + const propertyValues = {}; + + if (top) { + propertyValues['padding-top'] = { important, value: top }; + } + if (top && right) { + propertyValues['padding-right'] = { important, value: right }; + } + if (top && right && bottom) { + propertyValues['padding-bottom'] = { important, value: bottom }; + } + if (top && right && bottom && left) { + propertyValues['padding-left'] = { important, value: left }; + } + + return top ? propertyValues : null; + } + + /** + * Returns margin. + * + * @param value Value. + * @param important Important. + * @returns Property values. + */ + public static getMargin( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const parts = value.split(/ +/); + const top = parts[0] ? this.getMargin(parts[0]) : ''; + const right = parts[1] ? this.getMargin(parts[1]) : ''; + const bottom = parts[2] ? this.getMargin(parts[2]) : ''; + const left = parts[3] ? this.getMargin(parts[3]) : ''; + const propertyValues = {}; + + if (top) { + propertyValues['margin-top'] = { important, value: top }; + } + if (top && right) { + propertyValues['margin-right'] = { important, value: right }; + } + if (top && right && bottom) { + propertyValues['margin-bottom'] = { important, value: bottom }; + } + if (top && right && bottom && left) { + propertyValues['margin-left'] = { important, value: left }; + } + + return top ? propertyValues : null; + } + + /** + * Returns flex. + * + * @param name Name. + * @param value Value. + * @param important Important. + */ + public static getFlex(name: string, value: string, important: boolean): void { + const lowerValue = value.trim().toLowerCase(); + + switch (lowerValue) { + case 'none': + this.properties['flex-grow'] = { important, value: '0' }; + this.properties['flex-shrink'] = { important, value: '0' }; + this.properties['flex-basis'] = { important, value: 'auto' }; + return; + case 'auto': + this.properties['flex-grow'] = { important, value: '1' }; + this.properties['flex-shrink'] = { important, value: '1' }; + this.properties['flex-basis'] = { important, value: 'auto' }; + return; + case 'initial': + this.properties['flex-grow'] = { important, value: '0' }; + this.properties['flex-shrink'] = { important, value: '0' }; + this.properties['flex-basis'] = { important, value: 'auto' }; + return; + case 'inherit': + this.properties['flex-grow'] = { important, value: 'inherit' }; + this.properties['flex-shrink'] = { important, value: 'inherit' }; + this.properties['flex-basis'] = { important, value: 'inherit' }; + return; + } + + const parts = value.split(/ +/); + const flexGrow = parts[0] ? this.getInteger(parts[0]) : ''; + const flexShrink = parts[1] ? this.getInteger(parts[1]) : ''; + const flexBasis = parts[2] ? this.getFlexBasis(parts[2]) : ''; + + if (flexGrow && flexShrink && flexBasis) { + this.properties['flex-grow'] = { important, value: flexGrow }; + this.properties['flex-shrink'] = { important, value: flexShrink }; + this.properties['flex-basis'] = { important, value: flexBasis }; + } + } + + /** + * Returns background. + * + * @param name Name. + * @param value Value. + * @param important Important. + */ + public static getBackground(name: string, value: string, important: boolean): void { + const parts = value.split(/ +/); + + if (!parts[0]) { + return; + } + + // First value can be image if color is not specified. + if (!this.getColor(parts[0])) { + parts.unshift(''); + } + + const color = parts[0] ? this.getColor(parts[0]) : ''; + const image = parts[1] ? this.getURL(parts[1]) : ''; + const repeat = parts[2] ? this.getBackgroundRepeat(parts[2]) : ''; + const attachment = parts[3] ? this.getBackgroundAttachment(parts[3]) : ''; + const position = parts[4] ? this.getBackgroundPosition(parts[4]) : ''; + + if ((color || image) && repeat !== null && attachment !== null && position !== null) { + if (color) { + this.properties['background-color'] = { important, value: color }; + } + if (image) { + this.properties['background-image'] = { important, value: image }; + } + this.properties['background-repeat'] = { important, value: repeat }; + this.properties['background-attachment'] = { important, value: attachment }; + this.properties['background-position'] = { important, value: position }; + } + } + + /** + * Returns border top, right, bottom or left. + * + * @param name Name. + * @param value Value. + * @param important Important. + * @returns Property value. + */ + private static getBorderPosition( + name: string, + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const parts = value.split(/ +/); + const borderWidth = parts[0] ? this.getBorderWidth(parts[0]) : ''; + const borderStyle = parts[1] ? this.getBorderStyle(parts[1]) : ''; + const borderColor = parts[2] ? this.getColor(parts[2]) : ''; + const borderName = name.split('-')[1]; + + if (borderWidth && borderStyle !== null && borderColor !== null) { + return { + [`border-${borderName}-width`]: { important, value: borderWidth }, + [`border-${borderName}-style`]: { important, value: borderStyle }, + [`border-${borderName}-color`]: { important, value: borderColor } + }; + } + + return null; + } + + /** + * Returns margin. + * + * @param value Value. + * @returns Parsed value. + */ + public static getMarginValue(value: string): string { + const lowerValue = value.toLowerCase(); + if (lowerValue === 'auto') { + return lowerValue; + } + return this.getMeasurement(value); + } +} diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStyleString.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStyleString.ts index 5872a1014..2852ca413 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStyleString.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStyleString.ts @@ -1,5 +1,5 @@ import IElement from '../../../nodes/element/IElement'; -import ICSSStyleDeclarationProperty from '../ICSSStyleDeclarationProperty'; +import ICSSStyleDeclarationProperty from './ICSSStyleDeclarationPropertyValue'; import CSSStyleDeclarationPropertyManager from './CSSStyleDeclarationPropertyManager'; /** diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts deleted file mode 100644 index e7819ee50..000000000 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts +++ /dev/null @@ -1,420 +0,0 @@ -const COLOR_REGEXP = - /^#([0-9a-fA-F]{3,4}){1,2}$|^rgb\(([^)]*)\)$|^rgba\(([^)]*)\)$|^hsla?\(\s*(-?\d+|-?\d*.\d+)\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)%\s*(,\s*(-?\d+|-?\d*.\d+)\s*)?\)/; - -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 URL_REGEXP = /^url\(\s*([^)]*)\s*\)$/; -const RECT_REGEXP = /^rect\((.*)\)$/i; -const BORDER_STYLE = [ - 'none', - 'hidden', - 'dotted', - 'dashed', - 'solid', - 'double', - 'groove', - 'ridge', - '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 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 FONT_SIZE = [ - 'xx-small', - 'x-small', - 'small', - 'medium', - 'large', - 'x-large', - 'xx-large', - 'xxx-large', - 'smaller', - 'larger', - 'inherit', - 'initial', - 'revert', - 'unset' -]; - -/** - * Computed style property parser. - */ -export default class CSSStyleDeclarationValueParser { - /** - * Returns length. - * - * @param value Value. - * @returns Parsed value. - */ - public static getLength(value: string): string { - if (value === '0') { - return '0px'; - } - if (LENGTH_REGEXP.test(value)) { - return value; - } - return null; - } - - /** - * Returns percentance. - * - * @param value Value. - * @returns Parsed value. - */ - public static getPercentage(value: string): string { - if (value === '0') { - return '0%'; - } - if (PERCENTAGE_REGEXP.test(value)) { - return value; - } - return null; - } - - /** - * Returns measurement. - * - * @param value Value. - * @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); - } - - /** - * Returns measurement or auto. - * - * @param value Value. - * @returns Parsed value. - */ - public static getMeasurementOrAuto(value: string): string { - const lowerValue = value.toLowerCase(); - if (lowerValue === 'auto') { - return lowerValue; - } - return this.getMeasurement(value); - } - - /** - * Returns integer. - * - * @param value Value. - * @returns Parsed value. - */ - public static getInteger(value: string): string { - if (!isNaN(parseInt(value))) { - return value; - } - return null; - } - - /** - * Returns color. - * - * @param value Value. - * @returns Parsed value. - */ - public static getColor(value: string): string { - if (COLOR_REGEXP.test(value)) { - return value; - } - return null; - } - - /** - * Returns URL. - * - * Based on: - * https://github.com/jsdom/cssstyle/blob/master/lib/parsers.js#L222 - * - * @param value Value. - * @returns Parsed value. - */ - public static getURL(value: string): string { - if (!value) { - return null; - } - - const lowerValue = value.toLowerCase(); - - if (lowerValue === 'none' || lowerValue === 'inherit') { - return lowerValue; - } - - const result = URL_REGEXP.exec(value); - - if (!result) { - return null; - } - - let url = result[1]; - - if ((url[0] === '"' || url[0] === "'") && url[0] !== url[url.length - 1]) { - return null; - } - - if (url[0] === '"' || url[0] === "'") { - url = url.substring(1, url.length - 1); - } - - for (let i = 0; i < url.length; i++) { - switch (url[i]) { - case '(': - case ')': - case ' ': - case '\t': - case '\n': - case "'": - case '"': - return null; - case '\\': - i++; - break; - } - } - - return value; - } - - /** - * Returns border style. - * - * @param value Value. - * @returns Parsed value. - */ - public static getBorderStyle(value: string): string { - const lowerValue = value.toLowerCase(); - if (BORDER_STYLE.includes(lowerValue)) { - return lowerValue; - } - return null; - } - - /** - * Returns border collapse. - * - * @param value Value. - * @returns Parsed value. - */ - public static getBorderCollapse(value: string): string { - const lowerValue = value.toLowerCase(); - if (BORDER_COLLAPSE.includes(lowerValue)) { - return lowerValue; - } - return null; - } - - /** - * Returns background repeat. - * - * @param value Value. - * @returns Parsed value. - */ - public static getBackgroundRepeat(value: string): string { - const lowerValue = value.toLowerCase(); - if (BACKGROUND_REPEAT.includes(lowerValue)) { - return lowerValue; - } - return null; - } - - /** - * Returns background attachment. - * - * @param value Value. - * @returns Parsed value. - */ - public static getBackgroundAttachment(value: string): string { - const lowerValue = value.toLowerCase(); - if (BACKGROUND_ATTACHMENT.includes(lowerValue)) { - return lowerValue; - } - return null; - } - - /** - * Returns URL. - * - * Based on: - * https://github.com/jsdom/cssstyle/blob/master/lib/properties/backgroundPosition.js - * - * @param value Value. - * @returns Parsed value. - */ - public static getBackgroundPosition(value: string): string { - if (!value) { - return null; - } - const parts = value.split(/\s+/); - if (parts.length > 2 || parts.length < 1) { - return null; - } - if (parts.length === 1) { - if (this.getLength(parts[0])) { - return value; - } - if (parts[0]) { - const lowerValue = value.toLowerCase(); - if (BACKGROUND_POSITION.includes(lowerValue) || lowerValue === 'inherit') { - return lowerValue; - } - } - return null; - } - if ( - (this.getLength(parts[0]) || this.getPercentage(parts[0])) && - (this.getLength(parts[1]) || this.getPercentage(parts[1])) - ) { - return value.toLowerCase(); - } - if ( - BACKGROUND_POSITION.includes(parts[0].toLowerCase()) && - BACKGROUND_POSITION.includes(parts[1].toLowerCase()) - ) { - return `${parts[0].toLowerCase()} ${parts[1].toLowerCase()}`; - } - return null; - } - - /** - * Returns flex basis. - * - * @param value Value. - * @returns Parsed value. - */ - public static getFlexBasis(value: string): string { - const lowerValue = value.toLowerCase(); - if (FLEX_BASIS.includes(lowerValue)) { - return lowerValue; - } - return this.getLength(value); - } - - /** - * Returns margin. - * - * @param value Value. - * @returns Parsed value. - */ - public static getMargin(value: string): string { - const lowerValue = value.toLowerCase(); - if (lowerValue === 'auto') { - return lowerValue; - } - return this.getMeasurement(value); - } - - /** - * Returns border width. - * - * @param value Value. - * @returns Parsed value. - */ - public static getBorderWidth(value: string): string { - const lowerValue = value.toLowerCase(); - if (BORDER_WIDTH.includes(lowerValue)) { - return lowerValue; - } - return this.getLength(value); - } - - /** - * Returns clear. - * - * @param value Value. - * @returns Parsed value. - */ - public static getClear(value: string): string { - const lowerValue = value.toLowerCase(); - if (CLEAR.includes(lowerValue)) { - return lowerValue; - } - return null; - } - - /** - * Returns clip - * - * Based on: - * https://github.com/jsdom/cssstyle/blob/master/lib/properties/clip.js - * - * @param value Value. - * @returns Parsed value. - */ - public static getClip(value: string): string { - const lowerValue = value.toLowerCase(); - if (lowerValue === 'auto' || lowerValue === 'initial' || lowerValue === 'inherit') { - return lowerValue; - } - const matches = lowerValue.match(RECT_REGEXP); - if (!matches) { - return null; - } - const parts = matches[1].split(/\s*,\s*/); - if (parts.length !== 4) { - return null; - } - for (const part of parts) { - if (!this.getMeasurement(part)) { - return null; - } - } - return value; - } - - /** - * Returns float. - * - * @param value Value. - * @returns Parsed value. - */ - public static getFloat(value: string): string { - const lowerValue = value.toLowerCase(); - if (FLOAT.includes(lowerValue)) { - return lowerValue; - } - return null; - } - - /** - * Returns font size. - * - * @param value Value. - * @returns Parsed value. - */ - public static getFontSize(value: string): string { - const lowerValue = value.toLowerCase(); - if (FONT_SIZE.includes(lowerValue)) { - return lowerValue; - } - return this.getLength(value) || this.getPercentage(value); - } -} diff --git a/packages/happy-dom/src/css/declaration/utilities/ICSSStyleDeclarationPropertyValue.ts b/packages/happy-dom/src/css/declaration/utilities/ICSSStyleDeclarationPropertyValue.ts new file mode 100644 index 000000000..cbcfc9b7d --- /dev/null +++ b/packages/happy-dom/src/css/declaration/utilities/ICSSStyleDeclarationPropertyValue.ts @@ -0,0 +1,4 @@ +export default interface ICSSStyleDeclarationPropertyValue { + value: string; + important: boolean; +} From ecb00443476b276087ddb9a283cd398a519e4edd Mon Sep 17 00:00:00 2001 From: David Ortner Date: Fri, 12 Aug 2022 18:50:05 +0200 Subject: [PATCH 17/84] #344@trivial: Continue on CSSStyleDeclaration. --- .../CSSStyleDeclarationPropertyReader.ts | 108 --- .../CSSStyleDeclarationPropertyValueParser.ts | 629 ++++++++++-------- .../CSSStyleDeclarationValueParser.ts | 157 +++++ 3 files changed, 505 insertions(+), 389 deletions(-) delete mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyReader.ts create mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyReader.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyReader.ts deleted file mode 100644 index 9e2128339..000000000 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyReader.ts +++ /dev/null @@ -1,108 +0,0 @@ -import ICSSStyleDeclarationProperty from './ICSSStyleDeclarationPropertyValue'; -/** - * Computed style property parser. - */ -export default class CSSStyleDeclarationPropertyReader { - /** - * Returns property value. - * - * @param style Style. - * @param propertyName Property name. - * @returns Property value. - */ - public static getValue( - style: { - [k: string]: ICSSStyleDeclarationProperty; - }, - propertyName: string - ): string { - switch (propertyName) { - case 'margin': - if (!style['margin-top']?.value) { - return ''; - } - return `${style['margin-top']?.value} ${style['margin-right']?.value} ${style['margin-bottom']?.value} ${style['margin-left']?.value}` - .replace(/ /g, '') - .trim(); - case 'padding': - if (!style['padding-top']?.value) { - return ''; - } - return `${style['padding-top']?.value} ${style['padding-right']?.value} ${style['padding-bottom']?.value} ${style['padding-left']?.value}` - .replace(/ /g, '') - .trim(); - case 'border': - if ( - !style['border-top-width']?.value || - !style['border-top-style']?.value || - !style['border-top-color']?.value || - style['border-right-width']?.value !== style['border-top-width']?.value || - style['border-right-style']?.value !== style['border-top-style']?.value || - style['border-right-color']?.value !== style['border-top-color']?.value || - style['border-bottom-width']?.value !== style['border-top-width']?.value || - style['border-bottom-style']?.value !== style['border-top-style']?.value || - style['border-bottom-color']?.value !== style['border-top-color']?.value || - style['border-left-width']?.value !== style['border-top-width']?.value || - style['border-left-style']?.value !== style['border-top-style']?.value || - style['border-left-color']?.value !== style['border-top-color']?.value - ) { - return ''; - } - return `${style['border-top-width'].value} ${style['border-top-style'].value} ${style['border-top-color'].value}`; - case 'border-left': - if ( - !style['border-left-width']?.value || - !style['border-left-style']?.value || - !style['border-left-color']?.value - ) { - return ''; - } - return `${style['border-left-width'].value} ${style['border-left-style'].value} ${style['border-left-color'].value}`; - case 'border-right': - if ( - !style['border-right-width']?.value || - !style['border-right-style']?.value || - !style['border-right-color']?.value - ) { - return ''; - } - return `${style['border-right-width'].value} ${style['border-right-style'].value} ${style['border-right-color'].value}`; - case 'border-top': - if ( - !style['border-top-width']?.value || - !style['border-top-style']?.value || - !style['border-top-color']?.value - ) { - return ''; - } - return `${style['border-top-width'].value} ${style['border-top-style'].value} ${style['border-top-color'].value}`; - case 'border-bottom': - if ( - !style['border-bottom-width']?.value || - !style['border-bottom-style']?.value || - !style['border-bottom-color']?.value - ) { - return ''; - } - return `${style['border-bottom-width'].value} ${style['border-bottom-style'].value} ${style['border-bottom-color'].value}`; - case 'background': - if (!style['background-color']?.value && !style['background-image']?.value) { - return ''; - } - return `${style['background-color']?.value} ${style['background-image']?.value} ${style['background-repeat']?.value} ${style['background-attachment']?.value} ${style['background-position']?.value}` - .replace(/ /g, '') - .trim(); - case 'flex': - if ( - !style['flex-grow']?.value || - !style['flex-shrink']?.value || - !style['flex-basis']?.value - ) { - return ''; - } - return `${style['flex-grow'].value} ${style['flex-shrink'].value} ${style['flex-basis'].value}`; - } - - return style[propertyName]?.value || ''; - } -} diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValueParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValueParser.ts index 7764f37ce..429299497 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValueParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValueParser.ts @@ -1,11 +1,6 @@ +import CSSStyleDeclarationValueParser from './CSSStyleDeclarationValueParser'; import ICSSStyleDeclarationPropertyValue from './ICSSStyleDeclarationPropertyValue'; -const COLOR_REGEXP = - /^#([0-9a-fA-F]{3,4}){1,2}$|^rgb\(([^)]*)\)$|^rgba\(([^)]*)\)$|^hsla?\(\s*(-?\d+|-?\d*.\d+)\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)%\s*(,\s*(-?\d+|-?\d*.\d+)\s*)?\)/; - -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 URL_REGEXP = /^url\(\s*([^)]*)\s*\)$/; const RECT_REGEXP = /^rect\((.*)\)$/i; const BORDER_STYLE = [ 'none', @@ -59,163 +54,22 @@ const FONT_SIZE = [ * Computed style property parser. */ export default class CSSStyleDeclarationPropertyValueParser { - /** - * Returns length. - * - * @param value Value. - * @returns Parsed value. - */ - public static getLength(value: string): string { - if (value === '0') { - return '0px'; - } - if (LENGTH_REGEXP.test(value)) { - return value; - } - return null; - } - - /** - * Returns percentance. - * - * @param value Value. - * @returns Parsed value. - */ - public static getPercentage(value: string): string { - if (value === '0') { - return '0%'; - } - if (PERCENTAGE_REGEXP.test(value)) { - return value; - } - return null; - } - - /** - * Returns measurement. - * - * @param value Value. - * @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); - } - - /** - * Returns measurement or auto. - * - * @param value Value. - * @returns Parsed value. - */ - public static getMeasurementOrAuto(value: string): string { - const lowerValue = value.toLowerCase(); - if (lowerValue === 'auto') { - return lowerValue; - } - return this.getMeasurement(value); - } - - /** - * Returns integer. - * - * @param value Value. - * @returns Parsed value. - */ - public static getInteger(value: string): string { - if (!isNaN(parseInt(value))) { - return value; - } - return null; - } - - /** - * Returns color. - * - * @param value Value. - * @returns Parsed value. - */ - public static getColor(value: string): string { - if (COLOR_REGEXP.test(value)) { - return value; - } - return null; - } - - /** - * Returns URL. - * - * Based on: - * https://github.com/jsdom/cssstyle/blob/master/lib/parsers.js#L222 - * - * @param value Value. - * @returns Parsed value. - */ - public static getURL(value: string): string { - if (!value) { - return null; - } - - const lowerValue = value.toLowerCase(); - - if (lowerValue === 'none' || lowerValue === 'inherit') { - return lowerValue; - } - - const result = URL_REGEXP.exec(value); - - if (!result) { - return null; - } - - let url = result[1]; - - if ((url[0] === '"' || url[0] === "'") && url[0] !== url[url.length - 1]) { - return null; - } - - if (url[0] === '"' || url[0] === "'") { - url = url.substring(1, url.length - 1); - } - - for (let i = 0; i < url.length; i++) { - switch (url[i]) { - case '(': - case ')': - case ' ': - case '\t': - case '\n': - case "'": - case '"': - return null; - case '\\': - i++; - break; - } - } - - return value; - } - /** * Returns border style. * * @param value Value. - * @returns Parsed value. + * @param important Important. + * @returns Property values */ - public static getBorderStyle(value: string): string { + public static getBorderStyle( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { const lowerValue = value.toLowerCase(); if (BORDER_STYLE.includes(lowerValue)) { - return lowerValue; + return { 'border-style': { value: lowerValue, important } }; } return null; } @@ -224,12 +78,18 @@ export default class CSSStyleDeclarationPropertyValueParser { * Returns border collapse. * * @param value Value. - * @returns Parsed value. + * @param important Important. + * @returns Property values */ - public static getBorderCollapse(value: string): string { + public static getBorderCollapse( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { const lowerValue = value.toLowerCase(); if (BORDER_COLLAPSE.includes(lowerValue)) { - return lowerValue; + return { 'border-collapse': { value: lowerValue, important } }; } return null; } @@ -238,12 +98,18 @@ export default class CSSStyleDeclarationPropertyValueParser { * Returns background repeat. * * @param value Value. - * @returns Parsed value. + * @param important Important. + * @returns Property values */ - public static getBackgroundRepeat(value: string): string { + public static getBackgroundRepeat( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { const lowerValue = value.toLowerCase(); if (BACKGROUND_REPEAT.includes(lowerValue)) { - return lowerValue; + return { 'background-repeat': { value: lowerValue, important } }; } return null; } @@ -252,12 +118,18 @@ export default class CSSStyleDeclarationPropertyValueParser { * Returns background attachment. * * @param value Value. - * @returns Parsed value. + * @param important Important. + * @returns Property values */ - public static getBackgroundAttachment(value: string): string { + public static getBackgroundAttachment( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { const lowerValue = value.toLowerCase(); if (BACKGROUND_ATTACHMENT.includes(lowerValue)) { - return lowerValue; + return { 'background-attachment': { value: lowerValue, important } }; } return null; } @@ -269,9 +141,15 @@ export default class CSSStyleDeclarationPropertyValueParser { * https://github.com/jsdom/cssstyle/blob/master/lib/properties/backgroundPosition.js * * @param value Value. - * @returns Parsed value. + * @param important Important. + * @returns Property values */ - public static getBackgroundPosition(value: string): string { + public static getBackgroundPosition( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { if (!value) { return null; } @@ -280,28 +158,35 @@ export default class CSSStyleDeclarationPropertyValueParser { return null; } if (parts.length === 1) { - if (this.getLength(parts[0])) { - return value; + if (CSSStyleDeclarationValueParser.getLength(parts[0])) { + return { 'background-position': { value, important } }; } if (parts[0]) { const lowerValue = value.toLowerCase(); if (BACKGROUND_POSITION.includes(lowerValue) || lowerValue === 'inherit') { - return lowerValue; + return { 'background-position': { value: lowerValue, important } }; } } return null; } if ( - (this.getLength(parts[0]) || this.getPercentage(parts[0])) && - (this.getLength(parts[1]) || this.getPercentage(parts[1])) + (CSSStyleDeclarationValueParser.getLength(parts[0]) || + CSSStyleDeclarationValueParser.getPercentage(parts[0])) && + (CSSStyleDeclarationValueParser.getLength(parts[1]) || + CSSStyleDeclarationValueParser.getPercentage(parts[1])) ) { - return value.toLowerCase(); + return { 'background-position': { value: value.toLowerCase(), important } }; } if ( BACKGROUND_POSITION.includes(parts[0].toLowerCase()) && BACKGROUND_POSITION.includes(parts[1].toLowerCase()) ) { - return `${parts[0].toLowerCase()} ${parts[1].toLowerCase()}`; + return { + 'background-position': { + value: `${parts[0].toLowerCase()} ${parts[1].toLowerCase()}`, + important + } + }; } return null; } @@ -310,54 +195,59 @@ export default class CSSStyleDeclarationPropertyValueParser { * Returns flex basis. * * @param value Value. - * @returns Parsed value. + * @param important Important. + * @returns Property values */ - public static getFlexBasis(value: string): string { + public static getFlexBasis( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { const lowerValue = value.toLowerCase(); if (FLEX_BASIS.includes(lowerValue)) { - return lowerValue; - } - return this.getLength(value); - } - - /** - * Returns margin. - * - * @param value Value. - * @returns Parsed value. - */ - public static getMarginValue(value: string): string { - const lowerValue = value.toLowerCase(); - if (lowerValue === 'auto') { - return lowerValue; + return { 'flex-basis': { value: lowerValue, important } }; } - return this.getMeasurement(value); + return { 'flex-basis': { value: CSSStyleDeclarationValueParser.getLength(value), important } }; } /** * Returns border width. * * @param value Value. - * @returns Parsed value. + * @param important Important. + * @returns Property values */ - public static getBorderWidth(value: string): string { + public static getBorderWidth( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { const lowerValue = value.toLowerCase(); if (BORDER_WIDTH.includes(lowerValue)) { - return lowerValue; + return { 'border-width': { value: lowerValue, important } }; } - return this.getLength(value); + const length = CSSStyleDeclarationValueParser.getLength(value); + return length ? { 'border-width': { value: length, important } } : null; } /** * Returns clear. * * @param value Value. - * @returns Parsed value. + * @param important Important. + * @returns Property values */ - public static getClear(value: string): string { + public static getClear( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { const lowerValue = value.toLowerCase(); if (CLEAR.includes(lowerValue)) { - return lowerValue; + return { clear: { value: lowerValue, important } }; } return null; } @@ -369,12 +259,18 @@ export default class CSSStyleDeclarationPropertyValueParser { * https://github.com/jsdom/cssstyle/blob/master/lib/properties/clip.js * * @param value Value. - * @returns Parsed value. + * @param important Important. + * @returns Property values */ - public static getClip(value: string): string { + public static getClip( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { const lowerValue = value.toLowerCase(); if (lowerValue === 'auto' || lowerValue === 'initial' || lowerValue === 'inherit') { - return lowerValue; + return { clip: { value: lowerValue, important } }; } const matches = lowerValue.match(RECT_REGEXP); if (!matches) { @@ -385,23 +281,29 @@ export default class CSSStyleDeclarationPropertyValueParser { return null; } for (const part of parts) { - if (!this.getMeasurement(part)) { + if (!CSSStyleDeclarationValueParser.getMeasurement(part)) { return null; } } - return value; + return { clip: { value, important } }; } /** * Returns float. * * @param value Value. - * @returns Parsed value. + * @param important Important. + * @returns Property values */ - public static getFloat(value: string): string { + public static getFloat( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { const lowerValue = value.toLowerCase(); if (FLOAT.includes(lowerValue)) { - return lowerValue; + return { float: { value: lowerValue, important } }; } return null; } @@ -410,14 +312,23 @@ export default class CSSStyleDeclarationPropertyValueParser { * Returns font size. * * @param value Value. - * @returns Parsed value. + * @param important Important. + * @returns Property values */ - public static getFontSize(value: string): string { + public static getFontSize( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { const lowerValue = value.toLowerCase(); if (FONT_SIZE.includes(lowerValue)) { - return lowerValue; + return { 'font-size': { value: lowerValue, important } }; } - return this.getLength(value) || this.getPercentage(value); + const measurement = + CSSStyleDeclarationValueParser.getLength(value) || + CSSStyleDeclarationValueParser.getPercentage(value); + return measurement ? { 'font-size': { value: measurement, important } } : null; } /** @@ -432,28 +343,33 @@ export default class CSSStyleDeclarationPropertyValueParser { important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const parts = value.split(/ +/); - const borderWidth = parts[0] ? this.getBorderWidth(parts[0]) : ''; - const borderStyle = parts[1] ? this.getBorderStyle(parts[1]) : ''; - const borderColor = parts[2] ? this.getColor(parts[2]) : ''; + const borderWidth = parts[0] ? this.getBorderWidth(parts[0], important) : ''; + const borderStyle = parts[1] ? this.getBorderStyle(parts[1], important) : ''; + const borderColor = parts[2] ? CSSStyleDeclarationValueParser.getColor(parts[2]) : ''; + const propertyValues = {}; - if (borderWidth && borderStyle !== null && borderColor !== null) { - return { - ['border-top-width']: { important, value: borderWidth }, - ['border-top-style']: { important, value: borderStyle }, - ['border-top-color']: { important, value: borderColor }, - ['border-right-width']: { important, value: borderWidth }, - ['border-right-style']: { important, value: borderStyle }, - ['border-right-color']: { important, value: borderColor }, - ['border-bottom-width']: { important, value: borderWidth }, - ['border-bottom-style']: { important, value: borderStyle }, - ['border-bottom-color']: { important, value: borderColor }, - ['border-left-width']: { important, value: borderWidth }, - ['border-left-style']: { important, value: borderStyle }, - ['border-left-color']: { important, value: borderColor } - }; + if (borderWidth) { + propertyValues['border-top-width'] = { ...borderWidth['border-width'] }; + propertyValues['border-right-width'] = { ...borderWidth['border-width'] }; + propertyValues['border-bottom-width'] = { ...borderWidth['border-width'] }; + propertyValues['border-left-width'] = { ...borderWidth['border-width'] }; } - return null; + if (borderStyle) { + propertyValues['border-top-style'] = { ...borderStyle['border-style'] }; + propertyValues['border-right-style'] = { ...borderStyle['border-style'] }; + propertyValues['border-bottom-style'] = { ...borderStyle['border-style'] }; + propertyValues['border-left-style'] = { ...borderStyle['border-style'] }; + } + + if (borderColor) { + propertyValues['border-top-style'] = { important, value: borderColor }; + propertyValues['border-right-style'] = { important, value: borderColor }; + propertyValues['border-bottom-style'] = { important, value: borderColor }; + propertyValues['border-left-style'] = { important, value: borderColor }; + } + + return borderWidth && borderStyle !== null && borderColor !== null ? propertyValues : null; } /** @@ -468,26 +384,28 @@ export default class CSSStyleDeclarationPropertyValueParser { important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const parts = value.split(/ +/); - const topLeft = parts[0] ? this.getMeasurement(parts[0]) : ''; - const topRight = parts[1] ? this.getMeasurement(parts[1]) : ''; - const bottomRight = parts[2] ? this.getMeasurement(parts[2]) : ''; - const bottomLeft = parts[3] ? this.getMeasurement(parts[3]) : ''; + const topLeft = parts[0] ? CSSStyleDeclarationValueParser.getMeasurement(parts[0]) : ''; + const topRight = parts[1] ? CSSStyleDeclarationValueParser.getMeasurement(parts[1]) : ''; + const bottomRight = parts[2] ? CSSStyleDeclarationValueParser.getMeasurement(parts[2]) : ''; + const bottomLeft = parts[3] ? CSSStyleDeclarationValueParser.getMeasurement(parts[3]) : ''; const propertyValues = {}; if (topLeft) { propertyValues['border-top-left-radius'] = { important, value: topLeft }; } - if (topLeft && topRight) { + if (topRight) { propertyValues['border-top-right-radius'] = { important, value: topRight }; } - if (topLeft && topRight && bottomRight) { + if (bottomRight) { propertyValues['border-bottom-right-radius'] = { important, value: bottomRight }; } - if (topLeft && topRight && bottomRight && bottomLeft) { + if (bottomLeft) { propertyValues['border-bottom-left-radius'] = { important, value: bottomLeft }; } - return topLeft ? propertyValues : null; + return topLeft && topRight !== null && bottomRight !== null && bottomLeft !== null + ? propertyValues + : null; } /** @@ -501,7 +419,7 @@ export default class CSSStyleDeclarationPropertyValueParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - return this.getBorderPosition('border-top', value, important); + return this.getBorderByPosition('top', value, important); } /** @@ -515,7 +433,7 @@ export default class CSSStyleDeclarationPropertyValueParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - return this.getBorderPosition('border-top', value, important); + return this.getBorderByPosition('right', value, important); } /** @@ -529,7 +447,7 @@ export default class CSSStyleDeclarationPropertyValueParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - return this.getBorderPosition('border-top', value, important); + return this.getBorderByPosition('bottom', value, important); } /** @@ -543,7 +461,7 @@ export default class CSSStyleDeclarationPropertyValueParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - return this.getBorderPosition('border-top', value, important); + return this.getBorderByPosition('left', value, important); } /** @@ -557,26 +475,82 @@ export default class CSSStyleDeclarationPropertyValueParser { important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const parts = value.split(/ +/); - const top = parts[0] ? this.getMeasurement(parts[0]) : ''; - const right = parts[1] ? this.getMeasurement(parts[1]) : ''; - const bottom = parts[2] ? this.getMeasurement(parts[2]) : ''; - const left = parts[3] ? this.getMeasurement(parts[3]) : ''; + const top = parts[0] ? this.getPaddingByPosition('top', parts[0], important) : ''; + const right = parts[1] ? this.getPaddingByPosition('right', parts[0], important) : ''; + const bottom = parts[2] ? this.getPaddingByPosition('bottom', parts[0], important) : ''; + const left = parts[3] ? this.getPaddingByPosition('left', parts[0], important) : ''; const propertyValues = {}; if (top) { - propertyValues['padding-top'] = { important, value: top }; + Object.assign(propertyValues, top); } - if (top && right) { - propertyValues['padding-right'] = { important, value: right }; + if (right) { + Object.assign(propertyValues, right); } - if (top && right && bottom) { - propertyValues['padding-bottom'] = { important, value: bottom }; + if (bottom) { + Object.assign(propertyValues, bottom); } - if (top && right && bottom && left) { - propertyValues['padding-left'] = { important, value: left }; + if (left) { + Object.assign(propertyValues, left); } - return top ? propertyValues : null; + return top && right !== null && bottom !== null && left !== null ? propertyValues : null; + } + + /** + * Returns padding top. + * + * @param value Value. + * @param important Important. + * @returns Property values. + */ + public static getPaddingTop( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + return this.getPaddingByPosition('top', value, important); + } + + /** + * Returns padding right. + * + * @param value Value. + * @param important Important. + * @returns Property values. + */ + public static getPaddingRight( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + return this.getPaddingByPosition('right', value, important); + } + + /** + * Returns padding bottom. + * + * @param value Value. + * @param important Important. + * @returns Property values. + */ + public static getPaddingBottom( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + return this.getPaddingByPosition('bottom', value, important); + } + + /** + * Returns padding left. + * + * @param value Value. + * @param important Important. + * @returns Property values. + */ + public static getPaddingLeft( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + return this.getPaddingByPosition('left', value, important); } /** @@ -591,26 +565,82 @@ export default class CSSStyleDeclarationPropertyValueParser { important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const parts = value.split(/ +/); - const top = parts[0] ? this.getMargin(parts[0]) : ''; - const right = parts[1] ? this.getMargin(parts[1]) : ''; - const bottom = parts[2] ? this.getMargin(parts[2]) : ''; - const left = parts[3] ? this.getMargin(parts[3]) : ''; + const top = parts[0] ? this.getMarginByPosition('top', parts[0], important) : ''; + const right = parts[1] ? this.getMarginByPosition('right', parts[0], important) : ''; + const bottom = parts[2] ? this.getMarginByPosition('bottom', parts[0], important) : ''; + const left = parts[3] ? this.getMarginByPosition('left', parts[0], important) : ''; const propertyValues = {}; if (top) { - propertyValues['margin-top'] = { important, value: top }; + Object.assign(propertyValues, top); } - if (top && right) { - propertyValues['margin-right'] = { important, value: right }; + if (right) { + Object.assign(propertyValues, right); } - if (top && right && bottom) { - propertyValues['margin-bottom'] = { important, value: bottom }; + if (bottom) { + Object.assign(propertyValues, bottom); } - if (top && right && bottom && left) { - propertyValues['margin-left'] = { important, value: left }; + if (left) { + Object.assign(propertyValues, left); } - return top ? propertyValues : null; + return top && right !== null && bottom !== null && left !== null ? propertyValues : null; + } + + /** + * Returns margin top. + * + * @param value Value. + * @param important Important. + * @returns Property values. + */ + public static getMarginTop( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + return this.getMarginByPosition('top', value, important); + } + + /** + * Returns margin right. + * + * @param value Value. + * @param important Important. + * @returns Property values. + */ + public static getMarginRight( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + return this.getMarginByPosition('right', value, important); + } + + /** + * Returns margin right. + * + * @param value Value. + * @param important Important. + * @returns Property values. + */ + public static getMarginBottom( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + return this.getMarginByPosition('bottom', value, important); + } + + /** + * Returns margin left. + * + * @param value Value. + * @param important Important. + * @returns Property values. + */ + public static getMarginLeft( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + return this.getMarginByPosition('left', value, important); } /** @@ -699,27 +729,26 @@ export default class CSSStyleDeclarationPropertyValueParser { /** * Returns border top, right, bottom or left. * - * @param name Name. + * @param position Position. * @param value Value. * @param important Important. * @returns Property value. */ - private static getBorderPosition( - name: string, + private static getBorderByPosition( + position: 'top' | 'right' | 'bottom' | 'left', value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const parts = value.split(/ +/); - const borderWidth = parts[0] ? this.getBorderWidth(parts[0]) : ''; - const borderStyle = parts[1] ? this.getBorderStyle(parts[1]) : ''; - const borderColor = parts[2] ? this.getColor(parts[2]) : ''; - const borderName = name.split('-')[1]; + const borderWidth = parts[0] ? this.getBorderWidth(parts[0], important) : ''; + const borderStyle = parts[1] ? this.getBorderStyle(parts[1], important) : ''; + const borderColor = parts[2] ? CSSStyleDeclarationValueParser.getColor(parts[2]) : ''; - if (borderWidth && borderStyle !== null && borderColor !== null) { + if (borderWidth && borderStyle && borderColor) { return { - [`border-${borderName}-width`]: { important, value: borderWidth }, - [`border-${borderName}-style`]: { important, value: borderStyle }, - [`border-${borderName}-color`]: { important, value: borderColor } + [`border-${position}-width`]: borderWidth['border-width'], + [`border-${position}-style`]: borderStyle['border-style'], + [`border-${position}-color`]: { important, value: borderColor } }; } @@ -729,14 +758,52 @@ export default class CSSStyleDeclarationPropertyValueParser { /** * Returns margin. * + * @param position Position. * @param value Value. + * @param important Important. * @returns Parsed value. */ - public static getMarginValue(value: string): string { + private static getMarginByPosition( + position: 'top' | 'right' | 'bottom' | 'left', + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const lowerValue = value.toLowerCase(); if (lowerValue === 'auto') { - return lowerValue; - } - return this.getMeasurement(value); + return { [`margin-${position}`]: { important, value: 'auto' } }; + } + const measurement = CSSStyleDeclarationValueParser.getMeasurement(value); + return measurement + ? { + [`margin-${position}`]: { + important, + value: measurement + } + } + : null; + } + + /** + * Returns padding. + * + * @param position Position. + * @param value Value. + * @param important Important. + * @returns Parsed value. + */ + private static getPaddingByPosition( + position: 'top' | 'right' | 'bottom' | 'left', + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const measurement = CSSStyleDeclarationValueParser.getMeasurement(value); + return measurement + ? { + [`padding-${position}`]: { + important, + value: measurement + } + } + : null; } } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts new file mode 100644 index 000000000..b04c9379f --- /dev/null +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts @@ -0,0 +1,157 @@ +const COLOR_REGEXP = + /^#([0-9a-fA-F]{3,4}){1,2}$|^rgb\(([^)]*)\)$|^rgba\(([^)]*)\)$|^hsla?\(\s*(-?\d+|-?\d*.\d+)\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)%\s*(,\s*(-?\d+|-?\d*.\d+)\s*)?\)/; + +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 URL_REGEXP = /^url\(\s*([^)]*)\s*\)$/; + +/** + * Style declaration value parser. + */ +export default class CSSStyleDeclarationValueParser { + /** + * Returns length. + * + * @param value Value. + * @returns Parsed value. + */ + public static getLength(value: string): string { + if (value === '0') { + return '0px'; + } + if (LENGTH_REGEXP.test(value)) { + return value; + } + return null; + } + /** + * Returns percentance. + * + * @param value Value. + * @returns Parsed value. + */ + public static getPercentage(value: string): string { + if (value === '0') { + return '0%'; + } + if (PERCENTAGE_REGEXP.test(value)) { + return value; + } + return null; + } + + /** + * Returns measurement. + * + * @param value Value. + * @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); + } + + /** + * Returns measurement or auto. + * + * @param value Value. + * @returns Parsed value. + */ + public static getMeasurementOrAuto(value: string): string { + const lowerValue = value.toLowerCase(); + if (lowerValue === 'auto') { + return lowerValue; + } + return this.getMeasurement(value); + } + + /** + * Returns integer. + * + * @param value Value. + * @returns Parsed value. + */ + public static getInteger(value: string): string { + if (!isNaN(parseInt(value))) { + return value; + } + return null; + } + + /** + * Returns color. + * + * @param value Value. + * @returns Parsed value. + */ + public static getColor(value: string): string { + if (COLOR_REGEXP.test(value)) { + return value; + } + return null; + } + + /** + * Returns URL. + * + * Based on: + * https://github.com/jsdom/cssstyle/blob/master/lib/parsers.js#L222 + * + * @param value Value. + * @returns Parsed value. + */ + public static getURL(value: string): string { + if (!value) { + return null; + } + + const lowerValue = value.toLowerCase(); + + if (lowerValue === 'none' || lowerValue === 'inherit') { + return lowerValue; + } + + const result = URL_REGEXP.exec(value); + + if (!result) { + return null; + } + + let url = result[1]; + + if ((url[0] === '"' || url[0] === "'") && url[0] !== url[url.length - 1]) { + return null; + } + + if (url[0] === '"' || url[0] === "'") { + url = url.substring(1, url.length - 1); + } + + for (let i = 0; i < url.length; i++) { + switch (url[i]) { + case '(': + case ')': + case ' ': + case '\t': + case '\n': + case "'": + case '"': + return null; + case '\\': + i++; + break; + } + } + + return value; + } +} From 7297af54e6ed79d237677e09e64984f1838b558e Mon Sep 17 00:00:00 2001 From: Coin Date: Mon, 15 Aug 2022 02:35:48 +0800 Subject: [PATCH 18/84] #0@patch: Fix parsing of childless elements whose tag names are in different case. --- packages/happy-dom/src/xml-parser/XMLParser.ts | 2 +- packages/happy-dom/test/xml-parser/XMLParser.test.ts | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/happy-dom/src/xml-parser/XMLParser.ts b/packages/happy-dom/src/xml-parser/XMLParser.ts index 1268f3e91..98ed92be7 100755 --- a/packages/happy-dom/src/xml-parser/XMLParser.ts +++ b/packages/happy-dom/src/xml-parser/XMLParser.ts @@ -93,7 +93,7 @@ export default class XMLParser { if (ChildLessElements.includes(tagName)) { let childLessMatch = null; while ((childLessMatch = markupRegexp.exec(data))) { - if (childLessMatch[2] === match[2] && childLessMatch[1]) { + if (childLessMatch[2].toLowerCase() === tagName && childLessMatch[1]) { markupRegexp.lastIndex -= childLessMatch[0].length; break; } diff --git a/packages/happy-dom/test/xml-parser/XMLParser.test.ts b/packages/happy-dom/test/xml-parser/XMLParser.test.ts index c2ef811a8..b45bdd059 100644 --- a/packages/happy-dom/test/xml-parser/XMLParser.test.ts +++ b/packages/happy-dom/test/xml-parser/XMLParser.test.ts @@ -373,5 +373,16 @@ describe('XMLParser', () => { `.replace(/[\s]/gm, '') ); }); + + it('Parses childless elements with start and end tag names in different case', () => { + const root = XMLParser.parse( + window.document, + ` + + ` + ); + + expect((root.children[0]).innerText).toBe(`console.log('hello')`); + }); }); }); From 94e7ae24879ea9571c0d56ab75a641e908eb07c1 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Wed, 17 Aug 2022 16:59:17 +0200 Subject: [PATCH 19/84] #344@trivial: Continue on CSSStyleDeclaration. --- .../CSSStyleDeclarationPropertyManager.ts | 117 +++---- .../CSSStyleDeclarationPropertyValueParser.ts | 331 ++++++++++++++---- 2 files changed, 316 insertions(+), 132 deletions(-) diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts index 67b3fd4a7..89b04a191 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts @@ -1,6 +1,7 @@ import ICSSStyleDeclarationPropertyValue from './ICSSStyleDeclarationPropertyValue'; import CSSStyleDeclarationValueParser from './CSSStyleDeclarationPropertyValueParser'; import CSSStyleDeclarationPropertyManagerPropertyNames from './CSSStyleDeclarationPropertyManagerPropertyNames'; +import CSSStyleDeclarationPropertyValueParser from './CSSStyleDeclarationPropertyValueParser'; /** * Computed this.properties property parser. @@ -170,123 +171,109 @@ export default class CSSStyleDeclarationPropertyManager { * @param important Important. */ public set(name: string, value: string, important: boolean): void { + let propertyValues = null; + switch (name) { case 'border': - this.setBorder(name, value, important); + propertyValues = CSSStyleDeclarationPropertyValueParser.getBorder(value, important); + break; + case 'border-top': + propertyValues = CSSStyleDeclarationPropertyValueParser.getBorderTop(value, important); break; case 'border-left': + propertyValues = CSSStyleDeclarationPropertyValueParser.getBorderLeft(value, important); + break; case 'border-bottom': + propertyValues = CSSStyleDeclarationPropertyValueParser.getBorderBottom(value, important); + break; case 'border-right': - case 'border-top': - this.setBorderPosition(name, value, important); + propertyValues = CSSStyleDeclarationPropertyValueParser.getBorderRight(value, important); break; case 'border-width': - value = CSSStyleDeclarationValueParser.getBorderWidth(value); - if (value) { - this.properties['border-top-width'] = { name, important, value }; - this.properties['border-right-width'] = { name, important, value }; - this.properties['border-bottom-width'] = { name, important, value }; - this.properties['border-left-width'] = { name, important, value }; - } + propertyValues = CSSStyleDeclarationPropertyValueParser.getBorderWidth(value, important); break; case 'border-style': - value = CSSStyleDeclarationValueParser.getBorderStyle(value); - if (value) { - this.properties['border-top-style'] = { name, important, value }; - this.properties['border-right-style'] = { name, important, value }; - this.properties['border-bottom-style'] = { name, important, value }; - this.properties['border-left-style'] = { name, important, value }; - } + propertyValues = CSSStyleDeclarationPropertyValueParser.getBorderStyle(value, important); break; case 'border-color': - value = CSSStyleDeclarationValueParser.getBorderCollapse(value); - if (value) { - this.properties['border-top-color'] = { name, important, value }; - this.properties['border-right-color'] = { name, important, value }; - this.properties['border-bottom-color'] = { name, important, value }; - this.properties['border-left-color'] = { name, important, value }; - } + propertyValues = CSSStyleDeclarationPropertyValueParser.getBorderCollapse(value, important); break; case 'border-radius': - this.setBorderRadius(name, value, important); + propertyValues = CSSStyleDeclarationPropertyValueParser.getBorderRadius(value, important); break; case 'border-collapse': - value = CSSStyleDeclarationValueParser.getBorderCollapse(value); - if (value) { - this.properties[name] = { name, important, value }; - } + propertyValues = CSSStyleDeclarationPropertyValueParser.getBorderCollapse(value, important); break; case 'clear': - value = CSSStyleDeclarationValueParser.getClear(value); - if (value) { - this.properties[name] = { name, important, value }; - } + propertyValues = CSSStyleDeclarationPropertyValueParser.getClear(value, important); + break; case 'clip': - value = CSSStyleDeclarationValueParser.getClip(value); - if (value) { - this.properties[name] = { name, important, value }; - } + propertyValues = CSSStyleDeclarationPropertyValueParser.getClip(value, important); break; case 'css-float': case 'float': - value = CSSStyleDeclarationValueParser.getFloat(value); - if (value) { - this.properties[name] = { name, important, value }; - } + propertyValues = CSSStyleDeclarationPropertyValueParser.getFloat(value, important); break; case 'flex': - this.setFlex(name, value, important); + propertyValues = CSSStyleDeclarationPropertyValueParser.getFlex(value, important); break; case 'flex-shrink': + propertyValues = CSSStyleDeclarationPropertyValueParser.getFlexShrink(value, important); + break; case 'flex-grow': - value = CSSStyleDeclarationValueParser.getInteger(value); - if (value) { - this.properties[name] = { name, important, value }; - } + propertyValues = CSSStyleDeclarationPropertyValueParser.getFlexGrow(value, important); break; case 'flex-basis': - value = CSSStyleDeclarationValueParser.getFlexBasis(value); - if (value) { - this.properties[name] = { name, important, value }; - } + propertyValues = CSSStyleDeclarationPropertyValueParser.getFlexBasis(value, important); break; case 'padding': - this.setPadding(name, value, important); + propertyValues = CSSStyleDeclarationPropertyValueParser.getPadding(value, important); break; case 'margin': - this.setMargin(name, value, important); + propertyValues = CSSStyleDeclarationPropertyValueParser.getMargin(value, important); break; case 'background': - this.setBackground(name, value, important); + propertyValues = CSSStyleDeclarationPropertyValueParser.getBackground(value, important); break; case 'top': + propertyValues = CSSStyleDeclarationPropertyValueParser.getTop(value, important); + break; case 'right': + propertyValues = CSSStyleDeclarationPropertyValueParser.getRight(value, important); + break; case 'bottom': + propertyValues = CSSStyleDeclarationPropertyValueParser.getBottom(value, important); + break; case 'left': - value = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); - if (value) { - this.properties[name] = { name, important, value }; - } + propertyValues = CSSStyleDeclarationPropertyValueParser.getLeft(value, important); break; case 'padding-top': + propertyValues = CSSStyleDeclarationPropertyValueParser.getPaddingTop(value, important); + break; case 'padding-bottom': + propertyValues = CSSStyleDeclarationPropertyValueParser.getPaddingBottom(value, important); + break; case 'padding-left': + propertyValues = CSSStyleDeclarationPropertyValueParser.getPaddingLeft(value, important); + break; case 'padding-right': - value = CSSStyleDeclarationValueParser.getMeasurement(value); - if (value) { - this.properties[name] = { name, important, value }; - } + propertyValues = CSSStyleDeclarationPropertyValueParser.getPaddingRight(value, important); break; case 'margin-top': + propertyValues = CSSStyleDeclarationPropertyValueParser.getMarginTop(value, important); + break; case 'margin-bottom': + propertyValues = CSSStyleDeclarationPropertyValueParser.getMarginBottom(value, important); + break; case 'margin-left': + propertyValues = CSSStyleDeclarationPropertyValueParser.getMarginLeft(value, important); + break; case 'margin-right': - value = CSSStyleDeclarationValueParser.getMargin(value); - if (value) { - this.properties[name] = { name, important, value }; - } + propertyValues = CSSStyleDeclarationPropertyValueParser.getMarginRight(value, important); break; case 'border-top-width': + propertyValues = CSSStyleDeclarationPropertyValueParser.getBorderTopWidth(value, important); + break; case 'border-bottom-width': case 'border-left-width': case 'border-right-width': diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValueParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValueParser.ts index 429299497..1959aa357 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValueParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValueParser.ts @@ -211,6 +211,108 @@ export default class CSSStyleDeclarationPropertyValueParser { return { 'flex-basis': { value: CSSStyleDeclarationValueParser.getLength(value), important } }; } + /** + * Returns flex shrink. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getFlexShrink( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const parsedValue = CSSStyleDeclarationValueParser.getInteger(value); + return parsedValue ? { 'flex-shrink': { value: parsedValue, important } } : null; + } + + /** + * Returns flex grow. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getFlexGrow( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const parsedValue = CSSStyleDeclarationValueParser.getInteger(value); + return parsedValue ? { 'flex-grow': { value: parsedValue, important } } : null; + } + + /** + * Returns top. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getTop( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const parsedValue = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + return parsedValue ? { top: { value: parsedValue, important } } : null; + } + + /** + * Returns top. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getRight( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const parsedValue = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + return parsedValue ? { right: { value: parsedValue, important } } : null; + } + + /** + * Returns top. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBottom( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const parsedValue = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + return parsedValue ? { bottom: { value: parsedValue, important } } : null; + } + + /** + * Returns top. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getLeft( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const parsedValue = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + return parsedValue ? { left: { value: parsedValue, important } } : null; + } + /** * Returns border width. * @@ -232,6 +334,74 @@ export default class CSSStyleDeclarationPropertyValueParser { return length ? { 'border-width': { value: length, important } } : null; } + /** + * Returns border width. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBorderTopWidth( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const borderWidth = this.getBorderWidth(value, important); + return borderWidth ? { 'border-top-width': borderWidth['border-width'] } : null; + } + + /** + * Returns border width. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBorderRightWidth( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const borderWidth = this.getBorderWidth(value, important); + return borderWidth ? { 'border-right-width': borderWidth['border-width'] } : null; + } + + /** + * Returns border width. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBorderBottomWidth( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const borderWidth = this.getBorderWidth(value, important); + return borderWidth ? { 'border-bottom-width': borderWidth['border-width'] } : null; + } + + /** + * Returns border width. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBorderLeftWidth( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const borderWidth = this.getBorderWidth(value, important); + return borderWidth ? { 'border-left-width': borderWidth['border-width'] } : null; + } + /** * Returns clear. * @@ -343,9 +513,9 @@ export default class CSSStyleDeclarationPropertyValueParser { important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { 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] ? CSSStyleDeclarationValueParser.getColor(parts[2]) : ''; + const borderWidth = parts[0] ? this.getBorderWidth(parts[0], important) : null; + const borderStyle = parts[1] ? this.getBorderStyle(parts[1], important) : null; + const borderColor = parts[2] ? CSSStyleDeclarationValueParser.getColor(parts[2]) : null; const propertyValues = {}; if (borderWidth) { @@ -384,10 +554,10 @@ export default class CSSStyleDeclarationPropertyValueParser { important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const parts = value.split(/ +/); - const topLeft = parts[0] ? CSSStyleDeclarationValueParser.getMeasurement(parts[0]) : ''; - const topRight = parts[1] ? CSSStyleDeclarationValueParser.getMeasurement(parts[1]) : ''; - const bottomRight = parts[2] ? CSSStyleDeclarationValueParser.getMeasurement(parts[2]) : ''; - const bottomLeft = parts[3] ? CSSStyleDeclarationValueParser.getMeasurement(parts[3]) : ''; + const topLeft = parts[0] ? CSSStyleDeclarationValueParser.getMeasurement(parts[0]) : null; + const topRight = parts[1] ? CSSStyleDeclarationValueParser.getMeasurement(parts[1]) : null; + const bottomRight = parts[2] ? CSSStyleDeclarationValueParser.getMeasurement(parts[2]) : null; + const bottomLeft = parts[3] ? CSSStyleDeclarationValueParser.getMeasurement(parts[3]) : null; const propertyValues = {}; if (topLeft) { @@ -475,10 +645,10 @@ export default class CSSStyleDeclarationPropertyValueParser { important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const parts = value.split(/ +/); - const top = parts[0] ? this.getPaddingByPosition('top', parts[0], important) : ''; - const right = parts[1] ? this.getPaddingByPosition('right', parts[0], important) : ''; - const bottom = parts[2] ? this.getPaddingByPosition('bottom', parts[0], important) : ''; - const left = parts[3] ? this.getPaddingByPosition('left', parts[0], important) : ''; + const top = parts[0] ? this.getPaddingByPosition('top', parts[0], important) : null; + const right = parts[1] ? this.getPaddingByPosition('right', parts[0], important) : null; + const bottom = parts[2] ? this.getPaddingByPosition('bottom', parts[0], important) : null; + const left = parts[3] ? this.getPaddingByPosition('left', parts[0], important) : null; const propertyValues = {}; if (top) { @@ -565,10 +735,10 @@ export default class CSSStyleDeclarationPropertyValueParser { important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const parts = value.split(/ +/); - const top = parts[0] ? this.getMarginByPosition('top', parts[0], important) : ''; - const right = parts[1] ? this.getMarginByPosition('right', parts[0], important) : ''; - const bottom = parts[2] ? this.getMarginByPosition('bottom', parts[0], important) : ''; - const left = parts[3] ? this.getMarginByPosition('left', parts[0], important) : ''; + const top = parts[0] ? this.getMarginByPosition('top', parts[0], important) : null; + const right = parts[1] ? this.getMarginByPosition('right', parts[0], important) : null; + const bottom = parts[2] ? this.getMarginByPosition('bottom', parts[0], important) : null; + const left = parts[3] ? this.getMarginByPosition('left', parts[0], important) : null; const propertyValues = {}; if (top) { @@ -646,56 +816,71 @@ export default class CSSStyleDeclarationPropertyValueParser { /** * Returns flex. * - * @param name Name. * @param value Value. * @param important Important. + * @returns Property values. */ - public static getFlex(name: string, value: string, important: boolean): void { + public static getFlex( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const lowerValue = value.trim().toLowerCase(); switch (lowerValue) { case 'none': - this.properties['flex-grow'] = { important, value: '0' }; - this.properties['flex-shrink'] = { important, value: '0' }; - this.properties['flex-basis'] = { important, value: 'auto' }; - return; + return { + 'flex-grow': { important, value: '0' }, + 'flex-shrink': { important, value: '0' }, + 'flex-basis': { important, value: 'auto' } + }; case 'auto': - this.properties['flex-grow'] = { important, value: '1' }; - this.properties['flex-shrink'] = { important, value: '1' }; - this.properties['flex-basis'] = { important, value: 'auto' }; - return; + return { + 'flex-grow': { important, value: '1' }, + 'flex-shrink': { important, value: '1' }, + 'flex-basis': { important, value: 'auto' } + }; case 'initial': - this.properties['flex-grow'] = { important, value: '0' }; - this.properties['flex-shrink'] = { important, value: '0' }; - this.properties['flex-basis'] = { important, value: 'auto' }; - return; + return { + 'flex-grow': { important, value: '0' }, + 'flex-shrink': { important, value: '0' }, + 'flex-basis': { important, value: 'auto' } + }; case 'inherit': - this.properties['flex-grow'] = { important, value: 'inherit' }; - this.properties['flex-shrink'] = { important, value: 'inherit' }; - this.properties['flex-basis'] = { important, value: 'inherit' }; - return; + return { + 'flex-grow': { important, value: 'inherit' }, + 'flex-shrink': { important, value: 'inherit' }, + 'flex-basis': { important, value: 'inherit' } + }; } const parts = value.split(/ +/); - const flexGrow = parts[0] ? this.getInteger(parts[0]) : ''; - const flexShrink = parts[1] ? this.getInteger(parts[1]) : ''; - const flexBasis = parts[2] ? this.getFlexBasis(parts[2]) : ''; + const flexGrow = parts[0] ? CSSStyleDeclarationValueParser.getInteger(parts[0]) : null; + const flexShrink = parts[1] ? CSSStyleDeclarationValueParser.getInteger(parts[1]) : null; + const flexBasis = parts[2] ? this.getFlexBasis(parts[2], important) : null; if (flexGrow && flexShrink && flexBasis) { - this.properties['flex-grow'] = { important, value: flexGrow }; - this.properties['flex-shrink'] = { important, value: flexShrink }; - this.properties['flex-basis'] = { important, value: flexBasis }; + return { + ...flexBasis, + 'flex-grow': { important, value: flexGrow }, + 'flex-shrink': { important, value: flexShrink } + }; } + + return null; } /** * Returns background. * - * @param name Name. + * @param name * @param value Value. * @param important Important. + * @returns Property values. */ - public static getBackground(name: string, value: string, important: boolean): void { + public static getBackground( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const parts = value.split(/ +/); if (!parts[0]) { @@ -703,27 +888,36 @@ export default class CSSStyleDeclarationPropertyValueParser { } // First value can be image if color is not specified. - if (!this.getColor(parts[0])) { + if (!CSSStyleDeclarationValueParser.getColor(parts[0])) { parts.unshift(''); } - const color = parts[0] ? this.getColor(parts[0]) : ''; - const image = parts[1] ? this.getURL(parts[1]) : ''; - const repeat = parts[2] ? this.getBackgroundRepeat(parts[2]) : ''; - const attachment = parts[3] ? this.getBackgroundAttachment(parts[3]) : ''; - const position = parts[4] ? this.getBackgroundPosition(parts[4]) : ''; + const color = parts[0] ? CSSStyleDeclarationValueParser.getColor(parts[0]) : null; + const image = parts[1] ? CSSStyleDeclarationValueParser.getURL(parts[1]) : null; + const repeat = parts[2] ? this.getBackgroundRepeat(parts[2], important) : null; + const attachment = parts[3] ? this.getBackgroundAttachment(parts[3], important) : null; + const position = parts[4] ? this.getBackgroundPosition(parts[4], important) : null; + const propertyValues = {}; - if ((color || image) && repeat !== null && attachment !== null && position !== null) { - if (color) { - this.properties['background-color'] = { important, value: color }; - } - if (image) { - this.properties['background-image'] = { important, value: image }; - } - this.properties['background-repeat'] = { important, value: repeat }; - this.properties['background-attachment'] = { important, value: attachment }; - this.properties['background-position'] = { important, value: position }; + if (color) { + propertyValues['background-color'] = { important, value: color }; + } else if (image) { + propertyValues['background-image'] = { important, value: image }; + } + + if (repeat) { + Object.assign(propertyValues, repeat); + } + + if (attachment) { + Object.assign(propertyValues, attachment); } + + if (position) { + Object.assign(propertyValues, position); + } + + return color || image ? propertyValues : null; } /** @@ -740,19 +934,22 @@ export default class CSSStyleDeclarationPropertyValueParser { important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { 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] ? CSSStyleDeclarationValueParser.getColor(parts[2]) : ''; + const borderWidth = parts[0] ? this.getBorderWidth(parts[0], important) : null; + const borderStyle = parts[1] ? this.getBorderStyle(parts[1], important) : null; + const borderColor = parts[2] ? CSSStyleDeclarationValueParser.getColor(parts[2]) : null; + const propertyValues = {}; - if (borderWidth && borderStyle && borderColor) { - return { - [`border-${position}-width`]: borderWidth['border-width'], - [`border-${position}-style`]: borderStyle['border-style'], - [`border-${position}-color`]: { important, value: borderColor } - }; + if (borderWidth !== null) { + propertyValues['border-' + position + '-width'] = borderWidth['border-width']; + } + if (borderStyle !== null) { + propertyValues['border-' + position + '-style'] = borderStyle['border-width']; + } + if (borderColor) { + propertyValues['border-' + position + '-color'] = { important, value: borderColor }; } - return null; + return borderWidth !== null ? propertyValues : null; } /** From d8452e68384af92cbb676264354629c945f1a3ab Mon Sep 17 00:00:00 2001 From: David Ortner Date: Fri, 19 Aug 2022 13:14:07 +0200 Subject: [PATCH 20/84] #344@trivial: Continue on CSSStyleDeclaration. --- .../CSSStyleDeclarationPropertyManager.ts | 474 +++++++--- .../CSSStyleDeclarationPropertyValueParser.ts | 864 ++++++++++++------ ...eDeclarationPropertyWriterPropertyNames.ts | 57 -- .../CSSStyleDeclarationValueParser.ts | 10 + 4 files changed, 960 insertions(+), 445 deletions(-) delete mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyWriterPropertyNames.ts diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts index 89b04a191..7c7656d4f 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts @@ -1,6 +1,4 @@ import ICSSStyleDeclarationPropertyValue from './ICSSStyleDeclarationPropertyValue'; -import CSSStyleDeclarationValueParser from './CSSStyleDeclarationPropertyValueParser'; -import CSSStyleDeclarationPropertyManagerPropertyNames from './CSSStyleDeclarationPropertyManagerPropertyNames'; import CSSStyleDeclarationPropertyValueParser from './CSSStyleDeclarationPropertyValueParser'; /** @@ -52,21 +50,39 @@ export default class CSSStyleDeclarationPropertyManager { if (!this.properties['margin-top']?.value) { return ''; } - return `${this.properties['margin-top']?.value} ${this.properties['margin-right']?.value} ${this.properties['margin-bottom']?.value} ${this.properties['margin-left']?.value}` + 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(); case 'padding': if (!this.properties['padding-top']?.value) { return ''; } - return `${this.properties['padding-top']?.value} ${this.properties['padding-right']?.value} ${this.properties['padding-bottom']?.value} ${this.properties['padding-left']?.value}` + 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(); case 'border': if ( !this.properties['border-top-width']?.value || - !this.properties['border-top-style']?.value || - !this.properties['border-top-color']?.value || this.properties['border-right-width']?.value !== this.properties['border-top-width']?.value || this.properties['border-right-style']?.value !== @@ -87,43 +103,102 @@ export default class CSSStyleDeclarationPropertyManager { ) { return ''; } - return `${this.properties['border-top-width'].value} ${this.properties['border-top-style'].value} ${this.properties['border-top-color'].value}`; + 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 || - !this.properties['border-left-style']?.value || - !this.properties['border-left-color']?.value - ) { + 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}`; + 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(); + 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(); + 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(); + case 'border-color': if ( - !this.properties['border-right-width']?.value || - !this.properties['border-right-style']?.value || - !this.properties['border-right-color']?.value + !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-right-width'].value} ${this.properties['border-right-style'].value} ${this.properties['border-right-color'].value}`; - case 'border-top': + return this.properties['border-top-color'].value; + case 'border-style': if ( - !this.properties['border-top-width']?.value || !this.properties['border-top-style']?.value || - !this.properties['border-top-color']?.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-width'].value} ${this.properties['border-top-style'].value} ${this.properties['border-top-color'].value}`; - case 'border-bottom': + return this.properties['border-top-style'].value; + case 'border-width': if ( - !this.properties['border-bottom-width']?.value || - !this.properties['border-bottom-style']?.value || - !this.properties['border-bottom-color']?.value + !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-bottom-width'].value} ${this.properties['border-bottom-style'].value} ${this.properties['border-bottom-color'].value}`; + return this.properties['border-top-width'].value; + 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(); case 'background': if ( !this.properties['background-color']?.value && @@ -131,7 +206,18 @@ export default class CSSStyleDeclarationPropertyManager { ) { return ''; } - return `${this.properties['background-color']?.value} ${this.properties['background-image']?.value} ${this.properties['background-repeat']?.value} ${this.properties['background-attachment']?.value} ${this.properties['background-position']?.value}` + 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(); case 'flex': @@ -143,6 +229,20 @@ export default class CSSStyleDeclarationPropertyManager { return ''; } return `${this.properties['flex-grow'].value} ${this.properties['flex-shrink'].value} ${this.properties['flex-basis'].value}`; + 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 this.properties[name]?.value || ''; @@ -154,12 +254,92 @@ export default class CSSStyleDeclarationPropertyManager { * @param name Property name. */ public remove(name: string): void { - if (CSSStyleDeclarationPropertyManagerPropertyNames[name]) { - for (const propertyName of CSSStyleDeclarationPropertyManagerPropertyNames[name]) { - delete this.properties[propertyName]; - } - } else { - delete this.properties[name]; + switch (name) { + case 'border': + delete this.properties['border-top-width']; + delete this.properties['border-right-width']; + delete this.properties['border-bottom-width']; + delete this.properties['border-left-width']; + delete this.properties['border-top-style']; + delete this.properties['border-right-style']; + delete this.properties['border-bottom-style']; + delete this.properties['border-left-style']; + delete this.properties['border-top-color']; + delete this.properties['border-right-color']; + delete this.properties['border-bottom-color']; + delete this.properties['border-left-color']; + break; + case 'border-left': + delete this.properties['border-left-width']; + delete this.properties['border-left-style']; + delete this.properties['border-left-color']; + break; + case 'border-bottom': + delete this.properties['border-bottom-width']; + delete this.properties['border-bottom-style']; + delete this.properties['border-bottom-color']; + break; + case 'border-right': + delete this.properties['border-right-width']; + delete this.properties['border-right-style']; + delete this.properties['border-right-color']; + break; + case 'border-top': + delete this.properties['border-top-width']; + delete this.properties['border-top-style']; + delete this.properties['border-top-color']; + break; + case 'border-width': + delete this.properties['border-top-width']; + delete this.properties['border-right-width']; + delete this.properties['border-bottom-width']; + delete this.properties['border-left-width']; + break; + case 'border-style': + delete this.properties['border-top-style']; + delete this.properties['border-right-style']; + delete this.properties['border-bottom-style']; + delete this.properties['border-left-style']; + break; + case 'border-color': + delete this.properties['border-top-color']; + delete this.properties['border-right-color']; + delete this.properties['border-bottom-color']; + delete this.properties['border-left-color']; + break; + case 'border-radius': + delete this.properties['border-top-left-radius']; + delete this.properties['border-top-right-radius']; + delete this.properties['border-bottom-right-radius']; + delete this.properties['border-bottom-left-radius']; + break; + case 'background': + delete this.properties['background-color']; + delete this.properties['background-image']; + delete this.properties['background-repeat']; + delete this.properties['background-attachment']; + delete this.properties['background-position']; + break; + case 'flex': + delete this.properties['flex-grow']; + delete this.properties['flex-shrink']; + delete this.properties['flex-basis']; + break; + case 'padding': + delete this.properties['padding-top']; + delete this.properties['padding-right']; + delete this.properties['padding-bottom']; + delete this.properties['padding-left']; + break; + case 'margin': + delete this.properties['margin-top']; + delete this.properties['margin-right']; + delete this.properties['margin-bottom']; + delete this.properties['margin-left']; + break; + default: + delete this.properties[name]; + break; } } @@ -171,157 +351,207 @@ export default class CSSStyleDeclarationPropertyManager { * @param important Important. */ public set(name: string, value: string, important: boolean): void { - let propertyValues = null; + let properties = null; switch (name) { case 'border': - propertyValues = CSSStyleDeclarationPropertyValueParser.getBorder(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getBorder(value, important); break; case 'border-top': - propertyValues = CSSStyleDeclarationPropertyValueParser.getBorderTop(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getBorderTop(value, important); break; - case 'border-left': - propertyValues = CSSStyleDeclarationPropertyValueParser.getBorderLeft(value, important); + case 'border-right': + properties = CSSStyleDeclarationPropertyValueParser.getBorderRight(value, important); break; case 'border-bottom': - propertyValues = CSSStyleDeclarationPropertyValueParser.getBorderBottom(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getBorderBottom(value, important); break; - case 'border-right': - propertyValues = CSSStyleDeclarationPropertyValueParser.getBorderRight(value, important); + case 'border-left': + properties = CSSStyleDeclarationPropertyValueParser.getBorderLeft(value, important); break; case 'border-width': - propertyValues = CSSStyleDeclarationPropertyValueParser.getBorderWidth(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getBorderWidth(value, important); break; case 'border-style': - propertyValues = CSSStyleDeclarationPropertyValueParser.getBorderStyle(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getBorderStyle(value, important); break; case 'border-color': - propertyValues = CSSStyleDeclarationPropertyValueParser.getBorderCollapse(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getBorderColor(value, important); + break; + case 'border-top-width': + properties = CSSStyleDeclarationPropertyValueParser.getBorderTopWidth(value, important); + break; + case 'border-right-width': + properties = CSSStyleDeclarationPropertyValueParser.getBorderRightWidth(value, important); + break; + case 'border-bottom-width': + properties = CSSStyleDeclarationPropertyValueParser.getBorderBottomWidth(value, important); + break; + case 'border-left-width': + properties = CSSStyleDeclarationPropertyValueParser.getBorderLeftWidth(value, important); + break; + case 'border-top-color': + properties = CSSStyleDeclarationPropertyValueParser.getBorderTopColor(value, important); + break; + case 'border-right-color': + properties = CSSStyleDeclarationPropertyValueParser.getBorderRightColor(value, important); + break; + case 'border-bottom-color': + properties = CSSStyleDeclarationPropertyValueParser.getBorderBottomColor(value, important); + break; + case 'border-left-color': + properties = CSSStyleDeclarationPropertyValueParser.getBorderLeftColor(value, important); + break; + case 'border-top-style': + properties = CSSStyleDeclarationPropertyValueParser.getBorderTopStyle(value, important); + break; + case 'border-right-style': + properties = CSSStyleDeclarationPropertyValueParser.getBorderRightStyle(value, important); + break; + case 'border-bottom-style': + properties = CSSStyleDeclarationPropertyValueParser.getBorderBottomStyle(value, important); + break; + case 'border-left-style': + properties = CSSStyleDeclarationPropertyValueParser.getBorderLeftStyle(value, important); break; case 'border-radius': - propertyValues = CSSStyleDeclarationPropertyValueParser.getBorderRadius(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getBorderRadius(value, important); + break; + case 'border-top-left-radius': + properties = CSSStyleDeclarationPropertyValueParser.getBorderTopLeftRadius( + value, + important + ); + break; + case 'border-top-right-radius': + properties = CSSStyleDeclarationPropertyValueParser.getBorderTopRightRadius( + value, + important + ); + break; + case 'border-bottom-right-radius': + properties = CSSStyleDeclarationPropertyValueParser.getBorderBottomRightRadius( + value, + important + ); + break; + case 'border-bottom-right-radius': + properties = CSSStyleDeclarationPropertyValueParser.getBorderBottomLeftRadius( + value, + important + ); break; case 'border-collapse': - propertyValues = CSSStyleDeclarationPropertyValueParser.getBorderCollapse(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getBorderCollapse(value, important); break; case 'clear': - propertyValues = CSSStyleDeclarationPropertyValueParser.getClear(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getClear(value, important); break; case 'clip': - propertyValues = CSSStyleDeclarationPropertyValueParser.getClip(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getClip(value, important); break; case 'css-float': + properties = CSSStyleDeclarationPropertyValueParser.getCSSFloat(value, important); + break; case 'float': - propertyValues = CSSStyleDeclarationPropertyValueParser.getFloat(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getFloat(value, important); break; case 'flex': - propertyValues = CSSStyleDeclarationPropertyValueParser.getFlex(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getFlex(value, important); break; case 'flex-shrink': - propertyValues = CSSStyleDeclarationPropertyValueParser.getFlexShrink(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getFlexShrink(value, important); break; case 'flex-grow': - propertyValues = CSSStyleDeclarationPropertyValueParser.getFlexGrow(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getFlexGrow(value, important); break; case 'flex-basis': - propertyValues = CSSStyleDeclarationPropertyValueParser.getFlexBasis(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getFlexBasis(value, important); break; case 'padding': - propertyValues = CSSStyleDeclarationPropertyValueParser.getPadding(value, important); - break; - case 'margin': - propertyValues = CSSStyleDeclarationPropertyValueParser.getMargin(value, important); - break; - case 'background': - propertyValues = CSSStyleDeclarationPropertyValueParser.getBackground(value, important); - break; - case 'top': - propertyValues = CSSStyleDeclarationPropertyValueParser.getTop(value, important); - break; - case 'right': - propertyValues = CSSStyleDeclarationPropertyValueParser.getRight(value, important); - break; - case 'bottom': - propertyValues = CSSStyleDeclarationPropertyValueParser.getBottom(value, important); - break; - case 'left': - propertyValues = CSSStyleDeclarationPropertyValueParser.getLeft(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getPadding(value, important); break; case 'padding-top': - propertyValues = CSSStyleDeclarationPropertyValueParser.getPaddingTop(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getPaddingTop(value, important); break; case 'padding-bottom': - propertyValues = CSSStyleDeclarationPropertyValueParser.getPaddingBottom(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getPaddingBottom(value, important); break; case 'padding-left': - propertyValues = CSSStyleDeclarationPropertyValueParser.getPaddingLeft(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getPaddingLeft(value, important); break; case 'padding-right': - propertyValues = CSSStyleDeclarationPropertyValueParser.getPaddingRight(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getPaddingRight(value, important); + break; + case 'margin': + properties = CSSStyleDeclarationPropertyValueParser.getMargin(value, important); break; case 'margin-top': - propertyValues = CSSStyleDeclarationPropertyValueParser.getMarginTop(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getMarginTop(value, important); break; case 'margin-bottom': - propertyValues = CSSStyleDeclarationPropertyValueParser.getMarginBottom(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getMarginBottom(value, important); break; case 'margin-left': - propertyValues = CSSStyleDeclarationPropertyValueParser.getMarginLeft(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getMarginLeft(value, important); break; case 'margin-right': - propertyValues = CSSStyleDeclarationPropertyValueParser.getMarginRight(value, important); + properties = CSSStyleDeclarationPropertyValueParser.getMarginRight(value, important); break; - case 'border-top-width': - propertyValues = CSSStyleDeclarationPropertyValueParser.getBorderTopWidth(value, important); + case 'background': + properties = CSSStyleDeclarationPropertyValueParser.getBackground(value, important); break; - case 'border-bottom-width': - case 'border-left-width': - case 'border-right-width': - value = CSSStyleDeclarationValueParser.getBorderWidth(value); - if (value) { - this.properties[name] = { name, important, value }; - } + case 'background-image': + properties = CSSStyleDeclarationPropertyValueParser.getBackgroundImage(value, important); + break; + case 'background-color': + properties = CSSStyleDeclarationPropertyValueParser.getBackgroundColor(value, important); + break; + case 'background-repeat': + properties = CSSStyleDeclarationPropertyValueParser.getBackgroundRepeat(value, important); + break; + case 'background-attachment': + properties = CSSStyleDeclarationPropertyValueParser.getBackgroundAttachment( + value, + important + ); + break; + case 'background-position': + properties = CSSStyleDeclarationPropertyValueParser.getBackgroundPosition(value, important); + break; + case 'top': + properties = CSSStyleDeclarationPropertyValueParser.getTop(value, important); + break; + case 'right': + properties = CSSStyleDeclarationPropertyValueParser.getRight(value, important); + break; + case 'bottom': + properties = CSSStyleDeclarationPropertyValueParser.getBottom(value, important); + break; + case 'left': + properties = CSSStyleDeclarationPropertyValueParser.getLeft(value, important); + break; + case 'font': + properties = CSSStyleDeclarationPropertyValueParser.getFont(value, important); break; case 'font-size': - value = CSSStyleDeclarationValueParser.getFontSize(value); - if (value) { - this.properties[name] = { name, important, value }; - } + properties = CSSStyleDeclarationPropertyValueParser.getFontSize(value, important); break; case 'color': - case 'flood-color': - case 'border-top-color': - case 'border-bottom-color': - case 'border-left-color': - case 'border-right-color': - value = CSSStyleDeclarationValueParser.getColor(value); - if (value) { - this.properties[name] = { name, important, value }; - } + properties = CSSStyleDeclarationPropertyValueParser.getColor(value, important); break; - case 'border-top-style': - case 'border-bottom-style': - case 'border-left-style': - case 'border-right-style': - value = CSSStyleDeclarationValueParser.getBorderStyle(value); - if (value) { - this.properties[name] = { name, important, value }; - } + case 'flood-color': + properties = CSSStyleDeclarationPropertyValueParser.getFloodColor(value, important); break; default: - if (value) { - this.properties[name] = { name, important, value }; - } + properties = value + ? { + [name]: { value, important } + } + : null; break; } - } - /** - * Reads a string. - * - * @param styleString Style string (e.g. "border: 2px solid red; font-size: 12px;"). - */ - private fromString(styleString: string): { - [k: string]: ICSSStyleDeclarationProperty; - } {} + Object.assign(this.properties, properties); + } } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValueParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValueParser.ts index 1959aa357..72aabd6a7 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValueParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyValueParser.ts @@ -33,6 +33,16 @@ const FLEX_BASIS = [ ]; 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 FONT_WEIGHT = ['normal', 'bold', 'bolder', 'lighter']; const FONT_SIZE = [ 'xx-small', 'x-small', @@ -54,26 +64,6 @@ const FONT_SIZE = [ * Computed style property parser. */ export default class CSSStyleDeclarationPropertyValueParser { - /** - * Returns border style. - * - * @param value Value. - * @param important Important. - * @returns Property values - */ - public static getBorderStyle( - value: string, - important: boolean - ): { - [key: string]: ICSSStyleDeclarationPropertyValue; - } { - const lowerValue = value.toLowerCase(); - if (BORDER_STYLE.includes(lowerValue)) { - return { 'border-style': { value: lowerValue, important } }; - } - return null; - } - /** * Returns border collapse. * @@ -192,57 +182,54 @@ export default class CSSStyleDeclarationPropertyValueParser { } /** - * Returns flex basis. + * Returns top. * * @param value Value. * @param important Important. * @returns Property values */ - public static getFlexBasis( + public static getTop( value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const lowerValue = value.toLowerCase(); - if (FLEX_BASIS.includes(lowerValue)) { - return { 'flex-basis': { value: lowerValue, important } }; - } - return { 'flex-basis': { value: CSSStyleDeclarationValueParser.getLength(value), important } }; + const parsedValue = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + return parsedValue ? { top: { value: parsedValue, important } } : null; } /** - * Returns flex shrink. + * Returns top. * * @param value Value. * @param important Important. * @returns Property values */ - public static getFlexShrink( + public static getRight( value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const parsedValue = CSSStyleDeclarationValueParser.getInteger(value); - return parsedValue ? { 'flex-shrink': { value: parsedValue, important } } : null; + const parsedValue = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + return parsedValue ? { right: { value: parsedValue, important } } : null; } /** - * Returns flex grow. + * Returns top. * * @param value Value. * @param important Important. * @returns Property values */ - public static getFlexGrow( + public static getBottom( value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const parsedValue = CSSStyleDeclarationValueParser.getInteger(value); - return parsedValue ? { 'flex-grow': { value: parsedValue, important } } : null; + const parsedValue = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + return parsedValue ? { bottom: { value: parsedValue, important } } : null; } /** @@ -252,65 +239,139 @@ export default class CSSStyleDeclarationPropertyValueParser { * @param important Important. * @returns Property values */ - public static getTop( + public static getLeft( value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { const parsedValue = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); - return parsedValue ? { top: { value: parsedValue, important } } : null; + return parsedValue ? { left: { value: parsedValue, important } } : null; } /** - * Returns top. + * Returns clear. * * @param value Value. * @param important Important. * @returns Property values */ - public static getRight( + public static getClear( value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const parsedValue = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); - return parsedValue ? { right: { value: parsedValue, important } } : null; + const lowerValue = value.toLowerCase(); + if (CLEAR.includes(lowerValue)) { + return { clear: { value: lowerValue, important } }; + } + return null; } /** - * Returns top. + * Returns clip + * + * Based on: + * https://github.com/jsdom/cssstyle/blob/master/lib/properties/clip.js * * @param value Value. * @param important Important. * @returns Property values */ - public static getBottom( + public static getClip( value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const parsedValue = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); - return parsedValue ? { bottom: { value: parsedValue, important } } : null; + const lowerValue = value.toLowerCase(); + if (lowerValue === 'auto' || lowerValue === 'initial' || lowerValue === 'inherit') { + return { clip: { value: lowerValue, important } }; + } + const matches = lowerValue.match(RECT_REGEXP); + if (!matches) { + return null; + } + const parts = matches[1].split(/\s*,\s*/); + if (parts.length !== 4) { + return null; + } + for (const part of parts) { + if (!CSSStyleDeclarationValueParser.getMeasurement(part)) { + return null; + } + } + return { clip: { value, important } }; } /** - * Returns top. + * Returns float. * * @param value Value. * @param important Important. * @returns Property values */ - public static getLeft( + public static getFloat( value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const parsedValue = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); - return parsedValue ? { left: { value: parsedValue, important } } : null; + const lowerValue = value.toLowerCase(); + if (FLOAT.includes(lowerValue)) { + return { float: { value: lowerValue, important } }; + } + return null; + } + + /** + * Returns float. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getCSSFloat( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const float = this.getFloat(value, important); + return float ? { 'css-float': float['float'] } : null; + } + + /** + * Returns border. + * + * @param value Value. + * @param important Important. + * @returns Property values. + */ + public static getBorder( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + 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 = {}; + + if (borderWidth) { + Object.assign(properties, borderWidth); + } + + if (borderStyle) { + Object.assign(properties, borderStyle); + } + + if (borderColor) { + Object.assign(properties, borderColor); + } + + return borderWidth && borderStyle !== null && borderColor !== null ? properties : null; } /** @@ -328,10 +389,70 @@ export default class CSSStyleDeclarationPropertyValueParser { } { const lowerValue = value.toLowerCase(); if (BORDER_WIDTH.includes(lowerValue)) { - return { 'border-width': { value: lowerValue, important } }; + return { + 'border-top-width': { value: lowerValue, important }, + 'border-right-width': { value: lowerValue, important }, + 'border-bottom-width': { value: lowerValue, important }, + 'border-left-width': { value: lowerValue, important } + }; } const length = CSSStyleDeclarationValueParser.getLength(value); - return length ? { 'border-width': { value: length, important } } : null; + return length + ? { + 'border-top-width': { value: length, important }, + 'border-right-width': { value: length, important }, + 'border-bottom-width': { value: length, important }, + 'border-left-width': { value: length, important } + } + : null; + } + /** + * Returns border style. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBorderStyle( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const lowerValue = value.toLowerCase(); + if (BORDER_STYLE.includes(lowerValue)) { + return { + 'border-top-style': { value: lowerValue, important }, + 'border-right-style': { value: lowerValue, important }, + 'border-bottom-style': { value: lowerValue, important }, + 'border-left-style': { value: lowerValue, important } + }; + } + return null; + } + + /** + * Returns border color. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBorderColor( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const color = CSSStyleDeclarationValueParser.getColor(value); + return color + ? { + 'border-top-color': { value: color, important }, + 'border-right-color': { value: color, important }, + 'border-bottom-color': { value: color, important }, + 'border-left-color': { value: color, important } + } + : null; } /** @@ -348,7 +469,7 @@ export default class CSSStyleDeclarationPropertyValueParser { [key: string]: ICSSStyleDeclarationPropertyValue; } { const borderWidth = this.getBorderWidth(value, important); - return borderWidth ? { 'border-top-width': borderWidth['border-width'] } : null; + return borderWidth ? { 'border-top-width': borderWidth['border-top-width'] } : null; } /** @@ -365,7 +486,7 @@ export default class CSSStyleDeclarationPropertyValueParser { [key: string]: ICSSStyleDeclarationPropertyValue; } { const borderWidth = this.getBorderWidth(value, important); - return borderWidth ? { 'border-right-width': borderWidth['border-width'] } : null; + return borderWidth ? { 'border-right-width': borderWidth['border-right-width'] } : null; } /** @@ -382,7 +503,7 @@ export default class CSSStyleDeclarationPropertyValueParser { [key: string]: ICSSStyleDeclarationPropertyValue; } { const borderWidth = this.getBorderWidth(value, important); - return borderWidth ? { 'border-bottom-width': borderWidth['border-width'] } : null; + return borderWidth ? { 'border-bottom-width': borderWidth['border-bottom-width'] } : null; } /** @@ -399,147 +520,143 @@ export default class CSSStyleDeclarationPropertyValueParser { [key: string]: ICSSStyleDeclarationPropertyValue; } { const borderWidth = this.getBorderWidth(value, important); - return borderWidth ? { 'border-left-width': borderWidth['border-width'] } : null; + return borderWidth ? { 'border-left-width': borderWidth['border-left-width'] } : null; } /** - * Returns clear. + * Returns border style. * * @param value Value. * @param important Important. * @returns Property values */ - public static getClear( + public static getBorderTopStyle( value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const lowerValue = value.toLowerCase(); - if (CLEAR.includes(lowerValue)) { - return { clear: { value: lowerValue, important } }; - } - return null; + const borderStyle = this.getBorderStyle(value, important); + return borderStyle ? { 'border-top-style': borderStyle['border-top-style'] } : null; } /** - * Returns clip - * - * Based on: - * https://github.com/jsdom/cssstyle/blob/master/lib/properties/clip.js + * Returns border style. * * @param value Value. * @param important Important. * @returns Property values */ - public static getClip( + public static getBorderRightStyle( value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const lowerValue = value.toLowerCase(); - if (lowerValue === 'auto' || lowerValue === 'initial' || lowerValue === 'inherit') { - return { clip: { value: lowerValue, important } }; - } - const matches = lowerValue.match(RECT_REGEXP); - if (!matches) { - return null; - } - const parts = matches[1].split(/\s*,\s*/); - if (parts.length !== 4) { - return null; - } - for (const part of parts) { - if (!CSSStyleDeclarationValueParser.getMeasurement(part)) { - return null; - } - } - return { clip: { value, important } }; + const borderStyle = this.getBorderStyle(value, important); + return borderStyle ? { 'border-right-style': borderStyle['border-right-style'] } : null; } /** - * Returns float. + * Returns border style. * * @param value Value. * @param important Important. * @returns Property values */ - public static getFloat( + public static getBorderBottomStyle( value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const lowerValue = value.toLowerCase(); - if (FLOAT.includes(lowerValue)) { - return { float: { value: lowerValue, important } }; - } - return null; + const borderStyle = this.getBorderStyle(value, important); + return borderStyle ? { 'border-bottom-style': borderStyle['border-bottom-style'] } : null; } /** - * Returns font size. + * Returns border style. * * @param value Value. * @param important Important. * @returns Property values */ - public static getFontSize( + public static getBorderLeftStyle( value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const lowerValue = value.toLowerCase(); - if (FONT_SIZE.includes(lowerValue)) { - return { 'font-size': { value: lowerValue, important } }; - } - const measurement = - CSSStyleDeclarationValueParser.getLength(value) || - CSSStyleDeclarationValueParser.getPercentage(value); - return measurement ? { 'font-size': { value: measurement, important } } : null; + const borderStyle = this.getBorderStyle(value, important); + return borderStyle ? { 'border-left-style': borderStyle['border-left-style'] } : null; } /** - * Returns border. + * Returns border color. * * @param value Value. * @param important Important. - * @returns Property values. + * @returns Property values */ - public static getBorder( + public static getBorderTopColor( value: string, important: boolean - ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const parts = value.split(/ +/); - const borderWidth = parts[0] ? this.getBorderWidth(parts[0], important) : null; - const borderStyle = parts[1] ? this.getBorderStyle(parts[1], important) : null; - const borderColor = parts[2] ? CSSStyleDeclarationValueParser.getColor(parts[2]) : null; - const propertyValues = {}; - - if (borderWidth) { - propertyValues['border-top-width'] = { ...borderWidth['border-width'] }; - propertyValues['border-right-width'] = { ...borderWidth['border-width'] }; - propertyValues['border-bottom-width'] = { ...borderWidth['border-width'] }; - propertyValues['border-left-width'] = { ...borderWidth['border-width'] }; - } + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const borderColor = this.getBorderColor(value, important); + return borderColor ? { 'border-top-color': borderColor['border-top-color'] } : null; + } - if (borderStyle) { - propertyValues['border-top-style'] = { ...borderStyle['border-style'] }; - propertyValues['border-right-style'] = { ...borderStyle['border-style'] }; - propertyValues['border-bottom-style'] = { ...borderStyle['border-style'] }; - propertyValues['border-left-style'] = { ...borderStyle['border-style'] }; - } + /** + * Returns border color. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBorderRightColor( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const borderColor = this.getBorderColor(value, important); + return borderColor ? { 'border-right-color': borderColor['border-right-color'] } : null; + } - if (borderColor) { - propertyValues['border-top-style'] = { important, value: borderColor }; - propertyValues['border-right-style'] = { important, value: borderColor }; - propertyValues['border-bottom-style'] = { important, value: borderColor }; - propertyValues['border-left-style'] = { important, value: borderColor }; - } + /** + * Returns border color. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBorderBottomColor( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const borderColor = this.getBorderColor(value, important); + return borderColor ? { 'border-bottom-color': borderColor['border-bottom-color'] } : null; + } - return borderWidth && borderStyle !== null && borderColor !== null ? propertyValues : null; + /** + * Returns border color. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBorderLeftColor( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const borderColor = this.getBorderColor(value, important); + return borderColor ? { 'border-left-color': borderColor['border-left-color'] } : null; } /** @@ -554,30 +671,85 @@ export default class CSSStyleDeclarationPropertyValueParser { important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const parts = value.split(/ +/); - const topLeft = parts[0] ? CSSStyleDeclarationValueParser.getMeasurement(parts[0]) : null; - const topRight = parts[1] ? CSSStyleDeclarationValueParser.getMeasurement(parts[1]) : null; - const bottomRight = parts[2] ? CSSStyleDeclarationValueParser.getMeasurement(parts[2]) : null; - const bottomLeft = parts[3] ? CSSStyleDeclarationValueParser.getMeasurement(parts[3]) : null; - const propertyValues = {}; - - if (topLeft) { - propertyValues['border-top-left-radius'] = { important, value: topLeft }; - } - if (topRight) { - propertyValues['border-top-right-radius'] = { important, value: topRight }; - } - if (bottomRight) { - propertyValues['border-bottom-right-radius'] = { important, value: bottomRight }; - } - if (bottomLeft) { - propertyValues['border-bottom-left-radius'] = { important, value: bottomLeft }; - } + const topLeft = parts[0] ? CSSStyleDeclarationValueParser.getMeasurement(parts[0]) : ''; + const topRight = parts[1] ? CSSStyleDeclarationValueParser.getMeasurement(parts[1]) : ''; + const bottomRight = parts[2] ? CSSStyleDeclarationValueParser.getMeasurement(parts[2]) : ''; + const bottomLeft = parts[3] ? CSSStyleDeclarationValueParser.getMeasurement(parts[3]) : ''; + const properties = {}; + + properties['border-top-left-radius'] = { important, value: topLeft }; + properties['border-top-right-radius'] = { important, value: topRight || topLeft }; + properties['border-bottom-right-radius'] = { important, value: bottomRight || topLeft }; + properties['border-bottom-left-radius'] = { + important, + value: bottomLeft || topRight || topLeft + }; return topLeft && topRight !== null && bottomRight !== null && bottomLeft !== null - ? propertyValues + ? properties : null; } + /** + * Returns border radius. + * + * @param value Value. + * @param important Important. + * @returns Property values. + */ + public static getBorderTopLeftRadius( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const radius = this.getBorderRadius(value, important); + return radius ? { 'border-top-left-radius': radius['border-top-left-radius'] } : null; + } + + /** + * Returns border radius. + * + * @param value Value. + * @param important Important. + * @returns Property values. + */ + public static getBorderTopRightRadius( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const radius = this.getBorderRadius(value, important); + return radius ? { 'border-top-right-radius': radius['border-top-right-radius'] } : null; + } + + /** + * Returns border radius. + * + * @param value Value. + * @param important Important. + * @returns Property values. + */ + public static getBorderBottomRightRadius( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const radius = this.getBorderRadius(value, important); + return radius ? { 'border-bottom-right-radius': radius['border-bottom-right-radius'] } : null; + } + + /** + * Returns border radius. + * + * @param value Value. + * @param important Important. + * @returns Property values. + */ + public static getBorderBottomLeftRadius( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const radius = this.getBorderRadius(value, important); + return radius ? { 'border-bottom-left-radius': radius['border-bottom-left-radius'] } : null; + } + /** * Returns border top, right, bottom or left. * @@ -589,7 +761,14 @@ export default class CSSStyleDeclarationPropertyValueParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - return this.getBorderByPosition('top', value, important); + const border = this.getBorder(value, important); + return border + ? { + 'border-top-width': border['border-top-width'], + 'border-top-style': border['border-top-style'], + 'border-top-color': border['border-top-color'] + } + : null; } /** @@ -603,7 +782,14 @@ export default class CSSStyleDeclarationPropertyValueParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - return this.getBorderByPosition('right', value, important); + const border = this.getBorder(value, important); + return border + ? { + 'border-right-width': border['border-right-width'], + 'border-right-style': border['border-right-style'], + 'border-right-color': border['border-right-color'] + } + : null; } /** @@ -617,7 +803,14 @@ export default class CSSStyleDeclarationPropertyValueParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - return this.getBorderByPosition('bottom', value, important); + const border = this.getBorder(value, important); + return border + ? { + 'border-bottom-width': border['border-bottom-width'], + 'border-bottom-style': border['border-bottom-style'], + 'border-bottom-color': border['border-bottom-color'] + } + : null; } /** @@ -631,7 +824,14 @@ export default class CSSStyleDeclarationPropertyValueParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - return this.getBorderByPosition('left', value, important); + const border = this.getBorder(value, important); + return border + ? { + 'border-left-width': border['border-left-width'], + 'border-left-style': border['border-left-style'], + 'border-left-color': border['border-left-color'] + } + : null; } /** @@ -645,26 +845,18 @@ export default class CSSStyleDeclarationPropertyValueParser { important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const parts = value.split(/ +/); - const top = parts[0] ? this.getPaddingByPosition('top', parts[0], important) : null; - const right = parts[1] ? this.getPaddingByPosition('right', parts[0], important) : null; - const bottom = parts[2] ? this.getPaddingByPosition('bottom', parts[0], important) : null; - const left = parts[3] ? this.getPaddingByPosition('left', parts[0], important) : null; - const propertyValues = {}; - - if (top) { - Object.assign(propertyValues, top); - } - if (right) { - Object.assign(propertyValues, right); - } - if (bottom) { - Object.assign(propertyValues, bottom); - } - if (left) { - Object.assign(propertyValues, left); - } - - return top && right !== null && bottom !== null && left !== null ? propertyValues : null; + const top = parts[0] ? CSSStyleDeclarationValueParser.getMeasurement(parts[0]) : ''; + const right = parts[1] ? CSSStyleDeclarationValueParser.getMeasurement(parts[1]) : ''; + const bottom = parts[2] ? CSSStyleDeclarationValueParser.getMeasurement(parts[2]) : ''; + const left = parts[3] ? CSSStyleDeclarationValueParser.getMeasurement(parts[3]) : ''; + const properties = {}; + + properties['padding-top'] = { important, value: top }; + properties['padding-right'] = { important, value: right || top }; + properties['padding-bottom'] = { important, value: bottom || top }; + properties['padding-left'] = { important, value: left || right || top }; + + return top && right !== null && bottom !== null && left !== null ? properties : null; } /** @@ -678,7 +870,8 @@ export default class CSSStyleDeclarationPropertyValueParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - return this.getPaddingByPosition('top', value, important); + const padding = CSSStyleDeclarationValueParser.getMeasurement(value); + return padding ? { 'padding-top': { value: padding, important } } : null; } /** @@ -692,7 +885,8 @@ export default class CSSStyleDeclarationPropertyValueParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - return this.getPaddingByPosition('right', value, important); + const padding = CSSStyleDeclarationValueParser.getMeasurement(value); + return padding ? { 'padding-right': { value: padding, important } } : null; } /** @@ -706,7 +900,8 @@ export default class CSSStyleDeclarationPropertyValueParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - return this.getPaddingByPosition('bottom', value, important); + const padding = CSSStyleDeclarationValueParser.getMeasurement(value); + return padding ? { 'padding-bottom': { value: padding, important } } : null; } /** @@ -720,7 +915,8 @@ export default class CSSStyleDeclarationPropertyValueParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - return this.getPaddingByPosition('left', value, important); + const padding = CSSStyleDeclarationValueParser.getMeasurement(value); + return padding ? { 'padding-left': { value: padding, important } } : null; } /** @@ -735,26 +931,18 @@ export default class CSSStyleDeclarationPropertyValueParser { important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const parts = value.split(/ +/); - const top = parts[0] ? this.getMarginByPosition('top', parts[0], important) : null; - const right = parts[1] ? this.getMarginByPosition('right', parts[0], important) : null; - const bottom = parts[2] ? this.getMarginByPosition('bottom', parts[0], important) : null; - const left = parts[3] ? this.getMarginByPosition('left', parts[0], important) : null; - const propertyValues = {}; - - if (top) { - Object.assign(propertyValues, top); - } - if (right) { - Object.assign(propertyValues, right); - } - if (bottom) { - Object.assign(propertyValues, bottom); - } - if (left) { - Object.assign(propertyValues, left); - } - - return top && right !== null && bottom !== null && left !== null ? propertyValues : null; + const top = parts[0] ? CSSStyleDeclarationValueParser.getMeasurementOrAuto(parts[0]) : ''; + const right = parts[1] ? CSSStyleDeclarationValueParser.getMeasurementOrAuto(parts[1]) : ''; + const bottom = parts[2] ? CSSStyleDeclarationValueParser.getMeasurementOrAuto(parts[2]) : ''; + const left = parts[3] ? CSSStyleDeclarationValueParser.getMeasurementOrAuto(parts[3]) : ''; + const properties = {}; + + properties['margin-top'] = { important, value: top }; + properties['margin-right'] = { important, value: right || top }; + properties['margin-bottom'] = { important, value: bottom || top }; + properties['margin-left'] = { important, value: left || right || top }; + + return top && right !== null && bottom !== null && left !== null ? properties : null; } /** @@ -768,7 +956,8 @@ export default class CSSStyleDeclarationPropertyValueParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - return this.getMarginByPosition('top', value, important); + const margin = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + return margin ? { 'margin-top': { value: margin, important } } : null; } /** @@ -782,7 +971,8 @@ export default class CSSStyleDeclarationPropertyValueParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - return this.getMarginByPosition('right', value, important); + const margin = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + return margin ? { 'margin-right': { value: margin, important } } : null; } /** @@ -796,7 +986,8 @@ export default class CSSStyleDeclarationPropertyValueParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - return this.getMarginByPosition('bottom', value, important); + const margin = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + return margin ? { 'margin-bottom': { value: margin, important } } : null; } /** @@ -810,7 +1001,8 @@ export default class CSSStyleDeclarationPropertyValueParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - return this.getMarginByPosition('left', value, important); + const margin = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + return margin ? { 'margin-left': { value: margin, important } } : null; } /** @@ -854,25 +1046,79 @@ export default class CSSStyleDeclarationPropertyValueParser { } const parts = value.split(/ +/); - const flexGrow = parts[0] ? CSSStyleDeclarationValueParser.getInteger(parts[0]) : null; - const flexShrink = parts[1] ? CSSStyleDeclarationValueParser.getInteger(parts[1]) : null; - const flexBasis = parts[2] ? this.getFlexBasis(parts[2], important) : null; + const flexGrow = parts[0] ? this.getFlexGrow(parts[0], important) : ''; + const flexShrink = parts[1] ? this.getFlexShrink(parts[1], important) : ''; + const flexBasis = parts[2] ? this.getFlexBasis(parts[2], important) : ''; if (flexGrow && flexShrink && flexBasis) { return { ...flexBasis, - 'flex-grow': { important, value: flexGrow }, - 'flex-shrink': { important, value: flexShrink } + ...flexGrow, + ...flexBasis }; } return null; } + /** + * Returns flex basis. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getFlexBasis( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const lowerValue = value.toLowerCase(); + if (FLEX_BASIS.includes(lowerValue)) { + return { 'flex-basis': { value: lowerValue, important } }; + } + return { 'flex-basis': { value: CSSStyleDeclarationValueParser.getLength(value), important } }; + } + + /** + * Returns flex shrink. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getFlexShrink( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const parsedValue = CSSStyleDeclarationValueParser.getInteger(value); + return parsedValue ? { 'flex-shrink': { value: parsedValue, important } } : null; + } + + /** + * Returns flex grow. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getFlexGrow( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const parsedValue = CSSStyleDeclarationValueParser.getInteger(value); + return parsedValue ? { 'flex-grow': { value: parsedValue, important } } : null; + } + /** * Returns background. * - * @param name + * @param name Name. * @param value Value. * @param important Important. * @returns Property values. @@ -892,115 +1138,201 @@ export default class CSSStyleDeclarationPropertyValueParser { parts.unshift(''); } - const color = parts[0] ? CSSStyleDeclarationValueParser.getColor(parts[0]) : null; - const image = parts[1] ? CSSStyleDeclarationValueParser.getURL(parts[1]) : null; - const repeat = parts[2] ? this.getBackgroundRepeat(parts[2], important) : null; - const attachment = parts[3] ? this.getBackgroundAttachment(parts[3], important) : null; - const position = parts[4] ? this.getBackgroundPosition(parts[4], important) : null; - const propertyValues = {}; + const color = parts[0] ? this.getBackgroundColor(parts[0], important) : ''; + const image = parts[1] ? this.getBackgroundImage(parts[1], important) : ''; + const repeat = parts[2] ? this.getBackgroundRepeat(parts[2], important) : ''; + const attachment = parts[3] ? this.getBackgroundAttachment(parts[3], important) : ''; + const position = parts[4] ? this.getBackgroundPosition(parts[4], important) : ''; + const properties = {}; if (color) { - propertyValues['background-color'] = { important, value: color }; - } else if (image) { - propertyValues['background-image'] = { important, value: image }; + Object.assign(properties, color); + } + + if (image) { + Object.assign(properties, image); } if (repeat) { - Object.assign(propertyValues, repeat); + Object.assign(properties, repeat); } if (attachment) { - Object.assign(propertyValues, attachment); + Object.assign(properties, attachment); } if (position) { - Object.assign(propertyValues, position); + Object.assign(properties, position); } - return color || image ? propertyValues : null; + return (color || image) && repeat !== null && attachment !== null && position !== null + ? properties + : null; } /** - * Returns border top, right, bottom or left. + * Returns background color. * - * @param position Position. * @param value Value. * @param important Important. * @returns Property value. */ - private static getBorderByPosition( - position: 'top' | 'right' | 'bottom' | 'left', + public static getBackgroundColor( value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const parts = value.split(/ +/); - const borderWidth = parts[0] ? this.getBorderWidth(parts[0], important) : null; - const borderStyle = parts[1] ? this.getBorderStyle(parts[1], important) : null; - const borderColor = parts[2] ? CSSStyleDeclarationValueParser.getColor(parts[2]) : null; - const propertyValues = {}; + const color = CSSStyleDeclarationValueParser.getColor(value); - if (borderWidth !== null) { - propertyValues['border-' + position + '-width'] = borderWidth['border-width']; - } - if (borderStyle !== null) { - propertyValues['border-' + position + '-style'] = borderStyle['border-width']; - } - if (borderColor) { - propertyValues['border-' + position + '-color'] = { important, value: borderColor }; - } + return color + ? { + ['background-color']: { important, value: color } + } + : null; + } + + /** + * Returns background image. + * + * @param value Value. + * @param important Important. + * @returns Property value. + */ + public static getBackgroundImage( + value: string, + important: boolean + ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const image = CSSStyleDeclarationValueParser.getURL(value); - return borderWidth !== null ? propertyValues : null; + return image + ? { + ['background-image']: { important, value: image } + } + : null; } /** - * Returns margin. + * Returns color. * - * @param position Position. * @param value Value. * @param important Important. - * @returns Parsed value. + * @returns Property value. */ - private static getMarginByPosition( - position: 'top' | 'right' | 'bottom' | 'left', + public static getColor( value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const lowerValue = value.toLowerCase(); - if (lowerValue === 'auto') { - return { [`margin-${position}`]: { important, value: 'auto' } }; - } - const measurement = CSSStyleDeclarationValueParser.getMeasurement(value); - return measurement + const color = CSSStyleDeclarationValueParser.getColor(value); + + return color ? { - [`margin-${position}`]: { - important, - value: measurement - } + ['color']: { important, value: color } } : null; } /** - * Returns padding. + * Returns color. * - * @param position Position. * @param value Value. * @param important Important. - * @returns Parsed value. + * @returns Property value. */ - private static getPaddingByPosition( - position: 'top' | 'right' | 'bottom' | 'left', + public static getFloodColor( value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const measurement = CSSStyleDeclarationValueParser.getMeasurement(value); - return measurement + const color = CSSStyleDeclarationValueParser.getColor(value); + + return color ? { - [`padding-${position}`]: { - important, - value: measurement - } + ['flood-color']: { important, value: color } } : null; } + + /** + * Returns font. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getFont( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const parts = value.split(/ +/); + let font; + let fontFamily; + let fontSize; + let fontStyle; + let fontVariant; + let fontWeight; + let lineHeight; + } + + /** + * Returns font variant. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getFontVariant( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const lowerValue = value.toLowerCase(); + return lowerValue === 'normal' || lowerValue === 'small-caps' + ? { 'font-size': { value: lowerValue, important } } + : null; + } + + /** + * Returns font weight. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getFontWeight( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const lowerValue = value.toLowerCase(); + 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; + } + + /** + * Returns font size. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getFontSize( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const lowerValue = value.toLowerCase(); + if (FONT_SIZE.includes(lowerValue)) { + return { 'font-size': { value: lowerValue, important } }; + } + const measurement = CSSStyleDeclarationValueParser.getLengthOrPercentage(value); + return measurement ? { 'font-size': { value: measurement, important } } : null; + } } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyWriterPropertyNames.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyWriterPropertyNames.ts deleted file mode 100644 index 63ba2c3ed..000000000 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyWriterPropertyNames.ts +++ /dev/null @@ -1,57 +0,0 @@ -export default { - border: [ - 'border-top-width', - 'border-right-width', - 'border-bottom-width', - 'border-left-width', - 'border-top-style', - 'border-right-style', - 'border-bottom-style', - 'border-left-style', - 'border-top-color', - 'border-right-color', - 'border-bottom-color', - 'border-left-color' - ], - ['border-left']: ['border-left-width', 'border-left-style', 'border-left-color'], - - ['border-bottom']: ['border-bottom-width', 'border-bottom-style', 'border-bottom-color'], - - ['border-right']: ['border-right-width', 'border-right-style', 'border-right-color'], - - ['border-top']: ['border-top-width', 'border-top-style', 'border-top-color'], - ['border-width']: [ - 'border-top-width', - 'border-right-width', - 'border-bottom-width', - 'border-left-width' - ], - ['border-style']: [ - 'border-top-style', - 'border-right-style', - 'border-bottom-style', - 'border-left-style' - ], - ['border-color']: [ - 'border-top-color', - 'border-right-color', - 'border-bottom-color', - 'border-left-color' - ], - ['border-radius']: [ - 'border-top-left-radius', - 'border-top-right-radius', - 'border-bottom-right-radius', - 'border-bottom-left-radius' - ], - ['background']: [ - 'background-color', - 'background-image', - 'background-repeat', - 'background-attachment', - 'background-position' - ], - ['flex']: ['flex-grow', 'flex-shrink', 'flex-basis'], - ['padding']: ['padding-top', 'padding-right', 'padding-bottom', 'padding-left'], - ['margin']: ['margin-top', 'margin-right', 'margin-bottom', 'margin-left'] -}; diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts index b04c9379f..214045e76 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts @@ -40,6 +40,16 @@ export default class CSSStyleDeclarationValueParser { return null; } + /** + * Returns length or percentage. + * + * @param value Value. + * @returns Parsed value. + */ + public static getLengthOrPercentage(value: string): string { + return this.getLength(value) || this.getPercentage(value); + } + /** * Returns measurement. * From 058c6f1f4b1cc4a10dd9be2ead57b59e1f640ead Mon Sep 17 00:00:00 2001 From: David Ortner Date: Mon, 22 Aug 2022 12:21:02 +0200 Subject: [PATCH 21/84] #344@trivial: Continues on CSSStyleDeclaration. --- .../AbstractCSSStyleDeclaration.ts | 143 ++---- .../utilities/CSSStyleDeclarationElement.ts | 24 + .../CSSStyleDeclarationPropertyGetParser.ts | 437 +++++++++++++++++ .../CSSStyleDeclarationPropertyManager.ts | 462 ++++++++---------- ...> CSSStyleDeclarationPropertySetParser.ts} | 247 ++++++++-- .../CSSStyleDeclarationStyleString.ts | 90 ---- .../CSSStyleDeclarationValueParser.ts | 36 +- .../ICSSStyleDeclarationPropertyValue.ts | 4 +- 8 files changed, 940 insertions(+), 503 deletions(-) create mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts create mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts rename packages/happy-dom/src/css/declaration/utilities/{CSSStyleDeclarationPropertyValueParser.ts => CSSStyleDeclarationPropertySetParser.ts} (85%) delete mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationStyleString.ts 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; } From 1cd16c83e1d786cc00a13f47c254cb84d64be624 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Mon, 22 Aug 2022 17:17:42 +0200 Subject: [PATCH 22/84] #344@trivial: Continue on CSSStyleDeclaration. --- .../utilities/CSSStyleDeclarationElement.ts | 137 +++++++++++++++++- 1 file changed, 134 insertions(+), 3 deletions(-) diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts index 0b93386a3..95e3856fc 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts @@ -1,4 +1,52 @@ +import IShadowRoot from '../../../nodes/shadow-root/IShadowRoot'; +import CSSStyleSheet from '../../../css/CSSStyleSheet'; import IElement from '../../../nodes/element/IElement'; +import IDocument from '../../../nodes/document/IDocument'; +import IHTMLElement from '../../../nodes/html-element/IHTMLElement'; +import IHTMLStyleElement from '../../../nodes/html-style-element/IHTMLStyleElement'; +import INode from '../../../nodes/node/INode'; +import INodeList from '../../../nodes/node/INodeList'; +import CSSStyleDeclarationPropertyManager from './CSSStyleDeclarationPropertyManager'; + +const INHERITED_PROPERTIES = [ + 'border-collapse', + 'border-spacing', + 'caption-side', + 'color', + 'cursor', + 'direction', + 'empty-cells', + 'font-family', + 'font-size', + 'font-style', + 'font-variant', + 'font-weight', + 'font-size-adjust', + 'font-stretch', + 'font', + 'letter-spacing', + 'line-height', + 'list-style-image', + 'list-style-position', + 'list-style-type', + 'list-style', + 'orphans', + 'quotes', + 'tab-size', + 'text-align', + 'text-align-last', + 'text-decoration-color', + 'text-indent', + 'text-justify', + 'text-shadow', + 'text-transform', + 'visibility', + 'white-space', + 'widows', + 'word-break', + 'word-spacing', + 'word-wrap' +]; /** * CSS Style Declaration utility @@ -12,13 +60,96 @@ export default class CSSStyleDeclarationElement { * @returns Element style properties. */ public static getElementStyle(element: IElement, computed: boolean): string { + let style = ''; if (computed) { - // TODO: Add logic for style sheets + style += this.getStyleSheetElementStyle(element); } if (element['_attributes']['style'] && element['_attributes']['style'].value) { - return element['_attributes']['style'].value; + style += element['_attributes']['style'].value; + } + + return style ? style : null; + } + + /** + * Returns style sheet element style. + * + * @param element Element. + * @returns Style sheet element style. + */ + private static getStyleSheetElementStyle(element: IElement): string { + const elements = this.getElementsWithStyle(element); + const inherited = {}; + + for (const element of elements) { + const propertyManager = new CSSStyleDeclarationPropertyManager(element.cssText); + Object.assign(propertyManager.properties, inherited); + for (const name of Object.keys(propertyManager.properties)) { + if (INHERITED_PROPERTIES.includes(name)) { + inherited[name] = propertyManager.properties[name]; + } + } + } + } + + /** + * Returns style sheets. + * + * @param element Element. + * @returns Style sheets. + */ + private static getElementsWithStyle( + element: IElement + ): Array<{ element: IElement; cssText: string }> { + const elements: Array<{ element: IElement; cssText: string }> = [{ element, cssText: null }]; + let shadowRootElements: Array<{ element: IElement; cssText: string }> = [ + { element, cssText: null } + ]; + let parent: INode | IShadowRoot = element.parentNode; + + while (parent) { + const styleAndElement = { element, cssText: null }; + elements.unshift(styleAndElement); + shadowRootElements.unshift(styleAndElement); + + parent = parent.parentNode; + + if (!parent) { + if ((parent).host) { + const styleSheets = >( + (parent).host.querySelectorAll('style') + ); + for (const styleSheet of styleSheets) { + this.applyStyle(shadowRootElements, styleSheet.sheet); + } + parent = (parent).host; + shadowRootElements = []; + } + } } - return null; + return elements; + } + + /** + * Returns style sheets. + * + * @param elements Elements. + * @param styleSheet Style sheet. + */ + private static applyStyle( + elements: Array<{ element: IElement; cssText: string }>, + styleSheet: CSSStyleSheet + ): void { + for (const rule of styleSheet.cssRules) { + for (const element of elements) { + const firstBracket = rule.cssText.indexOf('{'); + const lastBracket = rule.cssText.lastIndexOf('}'); + const cssText = rule.cssText.substring(firstBracket + 1, lastBracket); + if (element.element.matches(rule.cssText.substring(0, firstBracket))) { + element.cssText += cssText; + } + } + } } } From 1a7f0e27c389e296971e60ac664fe08c35caef74 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Tue, 23 Aug 2022 00:50:56 +0200 Subject: [PATCH 23/84] #344@trivial: Continues on CSSStyleDeclaration. --- packages/happy-dom/src/css/CSSRule.ts | 27 ++-- packages/happy-dom/src/css/CSSRuleTypeEnum.ts | 17 ++ .../AbstractCSSStyleDeclaration.ts | 26 +-- .../utilities/CSSStyleDeclarationElement.ts | 152 ++++++++++-------- .../CSSStyleDeclarationValueParser.ts | 3 +- .../html-style-element/HTMLStyleElement.ts | 2 +- packages/happy-dom/src/window/Window.ts | 3 +- packages/happy-dom/test/window/Window.test.ts | 23 ++- 8 files changed, 153 insertions(+), 100 deletions(-) create mode 100644 packages/happy-dom/src/css/CSSRuleTypeEnum.ts diff --git a/packages/happy-dom/src/css/CSSRule.ts b/packages/happy-dom/src/css/CSSRule.ts index 0294ae200..6ffd86b8c 100644 --- a/packages/happy-dom/src/css/CSSRule.ts +++ b/packages/happy-dom/src/css/CSSRule.ts @@ -1,22 +1,23 @@ import CSSStyleSheet from './CSSStyleSheet'; +import CSSRuleTypeEnum from './CSSRuleTypeEnum'; /** * CSSRule interface. */ export default class CSSRule { - public static STYLE_RULE = 1; - public static IMPORT_RULE = 3; - public static MEDIA_RULE = 4; - public static FONT_FACE_RULE = 5; - public static PAGE_RULE = 6; - public static KEYFRAMES_RULE = 7; - public static KEYFRAME_RULE = 8; - public static NAMESPACE_RULE = 10; - public static COUNTER_STYLE_RULE = 11; - public static SUPPORTS_RULE = 12; - public static DOCUMENT_RULE = 13; - public static FONT_FEATURE_VALUES_RULE = 14; - public static REGION_STYLE_RULE = 16; + public static STYLE_RULE = CSSRuleTypeEnum.styleRule; + public static IMPORT_RULE = CSSRuleTypeEnum.importRule; + public static MEDIA_RULE = CSSRuleTypeEnum.mediaRule; + public static FONT_FACE_RULE = CSSRuleTypeEnum.fontFaceRule; + public static PAGE_RULE = CSSRuleTypeEnum.pageRule; + public static KEYFRAMES_RULE = CSSRuleTypeEnum.keyframesRule; + public static KEYFRAME_RULE = CSSRuleTypeEnum.keyframeRule; + public static NAMESPACE_RULE = CSSRuleTypeEnum.namespaceRule; + public static COUNTER_STYLE_RULE = CSSRuleTypeEnum.counterStyleRule; + public static SUPPORTS_RULE = CSSRuleTypeEnum.supportsRule; + public static DOCUMENT_RULE = CSSRuleTypeEnum.documentRule; + public static FONT_FEATURE_VALUES_RULE = CSSRuleTypeEnum.fontFeatureValuesRule; + public static REGION_STYLE_RULE = CSSRuleTypeEnum.regionStyleRule; public parentRule: CSSRule = null; public parentStyleSheet: CSSStyleSheet = null; diff --git a/packages/happy-dom/src/css/CSSRuleTypeEnum.ts b/packages/happy-dom/src/css/CSSRuleTypeEnum.ts new file mode 100644 index 000000000..b7604f86d --- /dev/null +++ b/packages/happy-dom/src/css/CSSRuleTypeEnum.ts @@ -0,0 +1,17 @@ +enum CSSRuleTypeEnum { + styleRule = 1, + importRule = 3, + mediaRule = 4, + fontFaceRule = 5, + pageRule = 6, + keyframesRule = 7, + keyframeRule = 8, + namespaceRule = 10, + counterStyleRule = 11, + supportsRule = 12, + documentRule = 13, + fontFeatureValuesRule = 14, + regionStyleRule = 16 +} + +export default CSSRuleTypeEnum; diff --git a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts index 155af69ba..842970918 100644 --- a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts +++ b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts @@ -35,9 +35,7 @@ export default abstract class AbstractCSSStyleDeclaration { */ public get length(): number { if (this._ownerElement) { - const style = new CSSStyleDeclarationPropertyManager( - CSSStyleDeclarationElement.getElementStyle(this._ownerElement, this._computed) - ); + const style = CSSStyleDeclarationElement.getElementStyle(this._ownerElement, this._computed); return style.size(); } @@ -55,10 +53,7 @@ export default abstract class AbstractCSSStyleDeclaration { return ''; } - const style = new CSSStyleDeclarationPropertyManager( - CSSStyleDeclarationElement.getElementStyle(this._ownerElement, false) - ); - return style.toString(); + return CSSStyleDeclarationElement.getElementStyle(this._ownerElement, false).toString(); } return this._style.toString(); @@ -103,10 +98,9 @@ export default abstract class AbstractCSSStyleDeclaration { */ public item(index: number): string { if (this._ownerElement) { - const style = new CSSStyleDeclarationPropertyManager( - CSSStyleDeclarationElement.getElementStyle(this._ownerElement, this._computed) + return CSSStyleDeclarationElement.getElementStyle(this._ownerElement, this._computed).item( + index ); - return style.item(index); } return this._style.item(index); } @@ -139,9 +133,7 @@ export default abstract class AbstractCSSStyleDeclaration { this._ownerElement['_attributes']['style'].name = 'style'; } - const style = new CSSStyleDeclarationPropertyManager( - CSSStyleDeclarationElement.getElementStyle(this._ownerElement, false) - ); + const style = CSSStyleDeclarationElement.getElementStyle(this._ownerElement, false); style.set(name, value, !!priority); this._ownerElement['_attributes']['style'].value = style.toString(); @@ -166,9 +158,7 @@ export default abstract class AbstractCSSStyleDeclaration { } if (this._ownerElement) { - const style = new CSSStyleDeclarationPropertyManager( - CSSStyleDeclarationElement.getElementStyle(this._ownerElement, false) - ); + const style = CSSStyleDeclarationElement.getElementStyle(this._ownerElement, false); style.remove(name); const newCSSText = style.toString(); if (newCSSText) { @@ -189,9 +179,7 @@ export default abstract class AbstractCSSStyleDeclaration { */ public getPropertyValue(name: string): string { if (this._ownerElement) { - const style = new CSSStyleDeclarationPropertyManager( - CSSStyleDeclarationElement.getElementStyle(this._ownerElement, false) - ); + const style = CSSStyleDeclarationElement.getElementStyle(this._ownerElement, this._computed); return style.get(name)?.value || ''; } 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 index 95e3856fc..0fb39ba1e 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts @@ -1,12 +1,15 @@ import IShadowRoot from '../../../nodes/shadow-root/IShadowRoot'; -import CSSStyleSheet from '../../../css/CSSStyleSheet'; import IElement from '../../../nodes/element/IElement'; import IDocument from '../../../nodes/document/IDocument'; -import IHTMLElement from '../../../nodes/html-element/IHTMLElement'; import IHTMLStyleElement from '../../../nodes/html-style-element/IHTMLStyleElement'; -import INode from '../../../nodes/node/INode'; import INodeList from '../../../nodes/node/INodeList'; import CSSStyleDeclarationPropertyManager from './CSSStyleDeclarationPropertyManager'; +import ICSSStyleDeclarationPropertyValue from './ICSSStyleDeclarationPropertyValue'; +import NodeTypeEnum from '../../../nodes/node/NodeTypeEnum'; +import CSSRuleTypeEnum from '../../CSSRuleTypeEnum'; +import CSSMediaRule from '../../rules/CSSMediaRule'; +import CSSRule from '../../CSSRule'; +import CSSStyleRule from '../../rules/CSSStyleRule'; const INHERITED_PROPERTIES = [ 'border-collapse', @@ -59,96 +62,119 @@ export default class CSSStyleDeclarationElement { * @param [computed] Computed. * @returns Element style properties. */ - public static getElementStyle(element: IElement, computed: boolean): string { - let style = ''; + public static getElementStyle( + element: IElement, + computed: boolean + ): CSSStyleDeclarationPropertyManager { if (computed) { - style += this.getStyleSheetElementStyle(element); - } - if (element['_attributes']['style'] && element['_attributes']['style'].value) { - style += element['_attributes']['style'].value; + return this.getComputedElementStyle(element); } - return style ? style : null; + return new CSSStyleDeclarationPropertyManager(element['_attributes']['style']?.value); } /** - * Returns style sheet element style. + * Returns style sheets. * * @param element Element. - * @returns Style sheet element style. + * @returns Style sheets. */ - private static getStyleSheetElementStyle(element: IElement): string { - const elements = this.getElementsWithStyle(element); - const inherited = {}; + private static getComputedElementStyle(element: IElement): CSSStyleDeclarationPropertyManager { + const targetElement: { element: IElement; cssText: string } = { element, cssText: null }; + const parentElements: Array<{ element: IElement; cssText: string }> = []; + const inheritedProperties: { [k: string]: ICSSStyleDeclarationPropertyValue } = {}; + let shadowRootElements: Array<{ element: IElement; cssText: string }> = [targetElement]; + let currentNode: IElement | IShadowRoot | IDocument = ( + element.parentNode + ); - for (const element of elements) { - const propertyManager = new CSSStyleDeclarationPropertyManager(element.cssText); - Object.assign(propertyManager.properties, inherited); - for (const name of Object.keys(propertyManager.properties)) { - if (INHERITED_PROPERTIES.includes(name)) { - inherited[name] = propertyManager.properties[name]; - } - } + if (!element.isConnected) { + return new CSSStyleDeclarationPropertyManager(); } - } - /** - * Returns style sheets. - * - * @param element Element. - * @returns Style sheets. - */ - private static getElementsWithStyle( - element: IElement - ): Array<{ element: IElement; cssText: string }> { - const elements: Array<{ element: IElement; cssText: string }> = [{ element, cssText: null }]; - let shadowRootElements: Array<{ element: IElement; cssText: string }> = [ - { element, cssText: null } - ]; - let parent: INode | IShadowRoot = element.parentNode; - - while (parent) { - const styleAndElement = { element, cssText: null }; - elements.unshift(styleAndElement); - shadowRootElements.unshift(styleAndElement); - - parent = parent.parentNode; - - if (!parent) { - if ((parent).host) { + while (currentNode) { + const styleAndElement = { element: currentNode, cssText: null }; + + if (currentNode.nodeType === NodeTypeEnum.elementNode) { + parentElements.unshift(styleAndElement); + shadowRootElements.unshift(styleAndElement); + } + + currentNode = currentNode.parentNode; + + if (currentNode) { + if (currentNode === element.ownerDocument) { const styleSheets = >( - (parent).host.querySelectorAll('style') + element.ownerDocument.querySelectorAll('style') ); for (const styleSheet of styleSheets) { - this.applyStyle(shadowRootElements, styleSheet.sheet); + this.applyCSSTextToElements(shadowRootElements, styleSheet.sheet.cssRules); } - parent = (parent).host; + currentNode = null; + } else if ((currentNode).host) { + const styleSheets = >( + (currentNode).querySelectorAll('style') + ); + for (const styleSheet of styleSheets) { + this.applyCSSTextToElements(shadowRootElements, styleSheet.sheet.cssRules); + } + currentNode = (currentNode).host; shadowRootElements = []; } } } - return elements; + for (const parentElement of parentElements) { + const propertyManager = new CSSStyleDeclarationPropertyManager( + parentElement.cssText + (parentElement['_attributes']['style']?.value || '') + ); + for (const name of Object.keys(propertyManager.properties)) { + if (INHERITED_PROPERTIES.includes(name)) { + inheritedProperties[name] = propertyManager.properties[name]; + } + } + } + + const targetPropertyManager = new CSSStyleDeclarationPropertyManager( + targetElement.cssText + (targetElement['_attributes']['style']?.value || '') + ); + Object.assign(targetPropertyManager.properties, inheritedProperties); + + return targetPropertyManager; } /** - * Returns style sheets. + * Applies CSS text to elements. * * @param elements Elements. - * @param styleSheet Style sheet. + * @param cssRules CSS rules. */ - private static applyStyle( + private static applyCSSTextToElements( elements: Array<{ element: IElement; cssText: string }>, - styleSheet: CSSStyleSheet + cssRules: CSSRule[] ): void { - for (const rule of styleSheet.cssRules) { - for (const element of elements) { - const firstBracket = rule.cssText.indexOf('{'); - const lastBracket = rule.cssText.lastIndexOf('}'); - const cssText = rule.cssText.substring(firstBracket + 1, lastBracket); - if (element.element.matches(rule.cssText.substring(0, firstBracket))) { - element.cssText += cssText; + if (!elements.length) { + return; + } + + const defaultView = elements[0].element.ownerDocument.defaultView; + + for (const rule of cssRules) { + if (rule.type === CSSRuleTypeEnum.styleRule) { + for (const element of elements) { + const selectorText = (rule).selectorText; + + if (selectorText && element.element.matches(selectorText)) { + const firstBracket = rule.cssText.indexOf('{'); + const lastBracket = rule.cssText.lastIndexOf('}'); + element.cssText += rule.cssText.substring(firstBracket + 1, lastBracket); + } } + } else if ( + rule.type === CSSRuleTypeEnum.mediaRule && + defaultView.matchMedia((rule).conditionalText).matches + ) { + this.applyCSSTextToElements(elements, (rule).cssRules); } } } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts index f9e0a70ed..e3907d82c 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts @@ -5,6 +5,7 @@ const LENGTH_REGEXP = /^(0|[-+]?[0-9]*\.?[0-9]+(in|cm|em|mm|pt|pc|px|ex|rem|vh|v const PERCENTAGE_REGEXP = /^[-+]?[0-9]*\.?[0-9]+%$/; const DEGREE_REGEXP = /^[0-9]+deg$/; const URL_REGEXP = /^url\(\s*([^)]*)\s*\)$/; +const INTEGER_REGEXP = /^[0-9]+$/; const GLOBALS = ['inherit', 'initial', 'unset', 'revert']; /** @@ -90,7 +91,7 @@ export default class CSSStyleDeclarationValueParser { * @returns Parsed value. */ public static getInteger(value: string): string { - if (!isNaN(parseInt(value))) { + if (INTEGER_REGEXP.test(value)) { return value; } return null; diff --git a/packages/happy-dom/src/nodes/html-style-element/HTMLStyleElement.ts b/packages/happy-dom/src/nodes/html-style-element/HTMLStyleElement.ts index 9a73d2fbf..335d09c4c 100644 --- a/packages/happy-dom/src/nodes/html-style-element/HTMLStyleElement.ts +++ b/packages/happy-dom/src/nodes/html-style-element/HTMLStyleElement.ts @@ -22,8 +22,8 @@ export default class HTMLStyleElement extends HTMLElement implements IHTMLStyleE } if (!this._styleSheet) { this._styleSheet = new CSSStyleSheet(); - this._styleSheet.replaceSync(this.textContent); } + this._styleSheet.replaceSync(this.textContent); return this._styleSheet; } diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts index c4182a1dc..414522362 100644 --- a/packages/happy-dom/src/window/Window.ts +++ b/packages/happy-dom/src/window/Window.ts @@ -97,7 +97,6 @@ import { Buffer } from 'buffer'; import Base64 from '../base64/Base64'; import IDocument from '../nodes/document/IDocument'; import Attr from '../nodes/attr/Attr'; -import ComputedStyle from '../css/computed-style/ComputedStyle'; import IElement from '../nodes/element/IElement'; const ORIGINAL_SET_TIMEOUT = setTimeout; @@ -423,7 +422,7 @@ export default class Window extends EventTarget implements IWindow { * @returns CSS style declaration. */ public getComputedStyle(element: IElement): CSSStyleDeclaration { - return ComputedStyle.getComputedStyle(element); + return new CSSStyleDeclaration(element, true); } /** diff --git a/packages/happy-dom/test/window/Window.test.ts b/packages/happy-dom/test/window/Window.test.ts index 0a04af406..c498b0849 100644 --- a/packages/happy-dom/test/window/Window.test.ts +++ b/packages/happy-dom/test/window/Window.test.ts @@ -203,7 +203,7 @@ describe('Window', () => { describe('getComputedStyle()', () => { it('Returns a CSSStyleDeclaration object with computed styles that are live updated whenever the element styles are changed.', () => { - const element = window.document.createElement('div'); + const element = document.createElement('div'); const computedStyle = window.getComputedStyle(element); element.style.direction = 'rtl'; @@ -216,6 +216,27 @@ describe('Window', () => { expect(computedStyle.direction).toBe('rtl'); }); + + it('Returns a CSSStyleDeclaration object with computed styles from style sheets.', () => { + const parent = document.createElement('div'); + const element = document.createElement('div'); + const computedStyle = window.getComputedStyle(element); + const documentStyle = document.createElement('style'); + const elementStyle = document.createElement('style'); + + documentStyle.innerHTML = + 'div { font: 12px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; }'; + elementStyle.innerHTML = 'div { border: 1px solid #000; }'; + + parent.appendChild(elementStyle); + parent.appendChild(element); + + document.body.appendChild(documentStyle); + document.body.appendChild(parent); + + debugger; + expect(computedStyle.direction).toBe('rtl'); + }); }); describe('eval()', () => { From 22a17ba03bece3090b3cf1fbf03f7ea52b0ae64b Mon Sep 17 00:00:00 2001 From: David Ortner Date: Tue, 23 Aug 2022 21:33:50 +0200 Subject: [PATCH 24/84] #344@trivial: Continue on CSSStyleDeclaration. --- .../utilities/CSSStyleDeclarationElement.ts | 8 ++--- .../CSSStyleDeclarationPropertyGetParser.ts | 32 +++++++++++-------- .../CSSStyleDeclarationPropertyManager.ts | 14 ++++---- .../CSSStyleDeclarationPropertySetParser.ts | 14 ++++---- .../CSSStyleDeclarationValueParser.ts | 14 ++++++++ .../declaration/CSSStyleDeclaration.test.ts | 1 + packages/happy-dom/test/window/Window.test.ts | 3 +- 7 files changed, 53 insertions(+), 33 deletions(-) diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts index 0fb39ba1e..27f3b7c0f 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts @@ -80,7 +80,7 @@ export default class CSSStyleDeclarationElement { * @returns Style sheets. */ private static getComputedElementStyle(element: IElement): CSSStyleDeclarationPropertyManager { - const targetElement: { element: IElement; cssText: string } = { element, cssText: null }; + const targetElement: { element: IElement; cssText: string } = { element, cssText: '' }; const parentElements: Array<{ element: IElement; cssText: string }> = []; const inheritedProperties: { [k: string]: ICSSStyleDeclarationPropertyValue } = {}; let shadowRootElements: Array<{ element: IElement; cssText: string }> = [targetElement]; @@ -93,7 +93,7 @@ export default class CSSStyleDeclarationElement { } while (currentNode) { - const styleAndElement = { element: currentNode, cssText: null }; + const styleAndElement = { element: currentNode, cssText: '' }; if (currentNode.nodeType === NodeTypeEnum.elementNode) { parentElements.unshift(styleAndElement); @@ -126,7 +126,7 @@ export default class CSSStyleDeclarationElement { for (const parentElement of parentElements) { const propertyManager = new CSSStyleDeclarationPropertyManager( - parentElement.cssText + (parentElement['_attributes']['style']?.value || '') + parentElement.cssText + (parentElement.element['_attributes']['style']?.value || '') ); for (const name of Object.keys(propertyManager.properties)) { if (INHERITED_PROPERTIES.includes(name)) { @@ -136,7 +136,7 @@ export default class CSSStyleDeclarationElement { } const targetPropertyManager = new CSSStyleDeclarationPropertyManager( - targetElement.cssText + (targetElement['_attributes']['style']?.value || '') + targetElement.cssText + (targetElement.element['_attributes']['style']?.value || '') ); Object.assign(targetPropertyManager.properties, inheritedProperties); diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts index 8d314eb7b..e93d969db 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts @@ -28,7 +28,7 @@ export default class CSSStyleDeclarationPropertyGetParser { ? properties['margin-bottom']?.value || '' : '' } ${ - properties['margin-right'].value !== properties['margin-left']?.value + properties['margin-right']?.value !== properties['margin-left']?.value ? properties['margin-left']?.value || '' : '' }` @@ -61,7 +61,7 @@ export default class CSSStyleDeclarationPropertyGetParser { ? properties['padding-bottom']?.value || '' : '' } ${ - properties['padding-right'].value !== properties['padding-left']?.value + properties['padding-right']?.value !== properties['padding-left']?.value ? properties['padding-left']?.value || '' : '' }` @@ -324,16 +324,16 @@ export default class CSSStyleDeclarationPropertyGetParser { 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-right-radius']?.value || '' } ${ properties['border-top-left-radius'].value !== - properties['border-bottom-right-radius'].value - ? properties['border-bottom-right-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 || '' + properties['border-top-right-radius']?.value !== + properties['border-bottom-left-radius']?.value + ? properties['border-bottom-left-radius']?.value || '' : '' }` .replace(/ /g, '') @@ -415,6 +415,12 @@ export default class CSSStyleDeclarationPropertyGetParser { if (!properties['font-family']?.value || !properties['font-size']?.value) { return null; } + const sizeAndLineHeight = [properties['font-size'].value]; + + if (properties['line-height']?.value) { + sizeAndLineHeight.push(properties['line-height'].value); + } + return { important: ![ properties['font-style']?.important, @@ -425,11 +431,11 @@ export default class CSSStyleDeclarationPropertyGetParser { 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 || ''}` + value: `${properties['font-style']?.value || ''} ${properties['font-variant']?.value || ''} ${ + properties['font-weight']?.value || '' + } ${properties['font-stretch']?.value || ''} ${sizeAndLineHeight.join('/')} ${ + 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 57c94ddd6..be7a58b7c 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts @@ -480,22 +480,24 @@ export default class CSSStyleDeclarationPropertyManager { font: clone.get('font') }; - let result = ''; + const result = []; for (const name of Object.keys(groupProperties)) { if (groupProperties[name]) { - result += `${name}: ${groupProperties[name].values}${ - groupProperties[name].important ? ' !important' : '' - }; `; + result.push( + `${name}: ${groupProperties[name].value}${ + 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' : ''}; `; + result.push(`${name}: ${property.value}${property.important ? ' !important' : ''};`); } - return result; + return result.join(' '); } } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts index 3e4a977a4..6ca35fd37 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts @@ -1281,17 +1281,15 @@ export default class CSSStyleDeclarationPropertySetParser { parts.splice(3, 0, ''); } - if (parts.length <= 5 || !this.getLineHeight(parts[5], important)) { - parts.splice(5, 0, ''); - } + const [fontSizeValue, lineHeightValue] = parts[4].split('/'); const fontStyle = parts[0] ? this.getFontStyle(parts[0], important) : ''; - const fontVariant = parts[0] ? this.getFontVariant(parts[1], important) : ''; + const fontVariant = parts[1] ? 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 fontSize = fontSizeValue ? this.getFontSize(fontSizeValue, important) : ''; + const lineHeight = lineHeightValue ? this.getLineHeight(lineHeightValue, important) : ''; + const fontFamily = parts[5] ? this.getFontFamily(parts.slice(5).join(' '), important) : ''; const properties = {}; @@ -1452,7 +1450,7 @@ export default class CSSStyleDeclarationPropertySetParser { return { 'line-height': { value: 'normal', important } }; } const lineHeight = - CSSStyleDeclarationValueParser.getInteger(value) || + CSSStyleDeclarationValueParser.getFloat(value) || CSSStyleDeclarationValueParser.getMeasurement(value); return lineHeight ? { 'line-height': { value: lineHeight, important } } : null; } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts index e3907d82c..6544f3ea0 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts @@ -6,6 +6,7 @@ const PERCENTAGE_REGEXP = /^[-+]?[0-9]*\.?[0-9]+%$/; const DEGREE_REGEXP = /^[0-9]+deg$/; const URL_REGEXP = /^url\(\s*([^)]*)\s*\)$/; const INTEGER_REGEXP = /^[0-9]+$/; +const FLOAT_REGEXP = /^[0-9.]+$/; const GLOBALS = ['inherit', 'initial', 'unset', 'revert']; /** @@ -97,6 +98,19 @@ export default class CSSStyleDeclarationValueParser { return null; } + /** + * Returns float. + * + * @param value Value. + * @returns Parsed value. + */ + public static getFloat(value: string): string { + if (FLOAT_REGEXP.test(value)) { + return value; + } + return null; + } + /** * Returns color. * diff --git a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts index fc8e0d223..2131ae2e9 100644 --- a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts +++ b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts @@ -30,6 +30,7 @@ describe('CSSStyleDeclaration', () => { element.setAttribute('style', `border: 2px solid green;border-radius: 2px;font-size: 12px;`); + debugger; expect(declaration[0]).toBe('border'); expect(declaration[1]).toBe('border-radius'); expect(declaration[2]).toBe('font-size'); diff --git a/packages/happy-dom/test/window/Window.test.ts b/packages/happy-dom/test/window/Window.test.ts index c498b0849..91fcff592 100644 --- a/packages/happy-dom/test/window/Window.test.ts +++ b/packages/happy-dom/test/window/Window.test.ts @@ -234,8 +234,7 @@ describe('Window', () => { document.body.appendChild(documentStyle); document.body.appendChild(parent); - debugger; - expect(computedStyle.direction).toBe('rtl'); + expect(computedStyle.font).toBe('12px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;'); }); }); From 09c75fcea58f36cdcae7ef9a44c891b4e123ea7f Mon Sep 17 00:00:00 2001 From: David Ortner Date: Tue, 23 Aug 2022 22:32:37 +0200 Subject: [PATCH 25/84] #344@trivial: Continues on CSSStyleDeclaration. --- .../utilities/CSSStyleDeclarationElement.ts | 4 +- .../CSSStyleDeclarationPropertySetParser.ts | 6 +- .../CSSStyleDeclarationValueParser.ts | 148 ++++++++++++++++++ .../declaration/CSSStyleDeclaration.test.ts | 45 ++++-- 4 files changed, 190 insertions(+), 13 deletions(-) diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts index 27f3b7c0f..e68a6e21f 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts @@ -138,7 +138,9 @@ export default class CSSStyleDeclarationElement { const targetPropertyManager = new CSSStyleDeclarationPropertyManager( targetElement.cssText + (targetElement.element['_attributes']['style']?.value || '') ); - Object.assign(targetPropertyManager.properties, inheritedProperties); + + Object.assign(inheritedProperties, targetPropertyManager.properties); + targetPropertyManager.properties = inheritedProperties; return targetPropertyManager; } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts index 6ca35fd37..02ef54cbc 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts @@ -1341,12 +1341,12 @@ export default class CSSStyleDeclarationPropertySetParser { } { const lowerValue = value.toLowerCase(); if (FONT_STYLE.includes(lowerValue)) { - return { fontStyle: { value: lowerValue, important } }; + return { 'font-style': { 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 degree ? { 'font-style': { value: lowerValue, important } } : null; } return null; } @@ -1366,7 +1366,7 @@ export default class CSSStyleDeclarationPropertySetParser { } { const lowerValue = value.toLowerCase(); return lowerValue === 'normal' || lowerValue === 'small-caps' - ? { 'font-size': { value: lowerValue, important } } + ? { 'font-variant': { value: lowerValue, important } } : null; } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts index 6544f3ea0..e1efdb0f6 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts @@ -8,6 +8,150 @@ const URL_REGEXP = /^url\(\s*([^)]*)\s*\)$/; const INTEGER_REGEXP = /^[0-9]+$/; const FLOAT_REGEXP = /^[0-9.]+$/; const GLOBALS = ['inherit', 'initial', 'unset', 'revert']; +const COLORS = [ + 'silver', + 'gray', + 'white', + 'maroon', + 'red', + 'purple', + 'fuchsia', + 'green', + 'lime', + 'olive', + 'yellow', + 'navy', + 'blue', + 'teal', + 'aqua', + 'antiquewhite', + 'aquamarine', + 'azure', + 'beige', + 'bisque', + 'blanchedalmond', + 'blueviolet', + 'brown', + 'burlywood', + 'cadetblue', + 'chartreuse', + 'chocolate', + 'coral', + 'cornflowerblue', + 'cornsilk', + 'crimson', + 'darkblue', + 'darkcyan', + 'darkgoldenrod', + 'darkgray', + 'darkgreen', + 'darkgrey', + 'darkkhaki', + 'darkmagenta', + 'darkolivegreen', + 'darkorange', + 'darkorchid', + 'darkred', + 'darksalmon', + 'darkseagreen', + 'darkslateblue', + 'darkslategray', + 'darkslategrey', + 'darkturquoise', + 'darkviolet', + 'deeppink', + 'deepskyblue', + 'dimgray', + 'dimgrey', + 'dodgerblue', + 'firebrick', + 'floralwhite', + 'forestgreen', + 'gainsboro', + 'ghostwhite', + 'gold', + 'goldenrod', + 'greenyellow', + 'grey', + 'honeydew', + 'hotpink', + 'indianred', + 'indigo', + 'ivory', + 'khaki', + 'lavender', + 'lavenderblush', + 'lawngreen', + 'lemonchiffon', + 'lightblue', + 'lightcoral', + 'lightcyan', + 'lightgoldenrodyellow', + 'lightgray', + 'lightgreen', + 'lightgrey', + 'lightpink', + 'lightsalmon', + 'lightseagreen', + 'lightskyblue', + 'lightslategray', + 'lightslategrey', + 'lightsteelblue', + 'lightyellow', + 'limegreen', + 'linen', + 'mediumaquamarine', + 'mediumblue', + 'mediumorchid', + 'mediumpurple', + 'mediumseagreen', + 'mediumslateblue', + 'mediumspringgreen', + 'mediumturquoise', + 'mediumvioletred', + 'midnightblue', + 'mintcream', + 'mistyrose', + 'moccasin', + 'navajowhite', + 'oldlace', + 'olivedrab', + 'orangered', + 'orchid', + 'palegoldenrod', + 'palegreen', + 'paleturquoise', + 'palevioletred', + 'papayawhip', + 'peachpuff', + 'peru', + 'pink', + 'plum', + 'powderblue', + 'rosybrown', + 'royalblue', + 'saddlebrown', + 'salmon', + 'sandybrown', + 'seagreen', + 'seashell', + 'sienna', + 'skyblue', + 'slateblue', + 'slategray', + 'slategrey', + 'snow', + 'springgreen', + 'steelblue', + 'tan', + 'thistle', + 'tomato', + 'turquoise', + 'violet', + 'wheat', + 'whitesmoke', + 'yellowgreen' +]; /** * Style declaration value parser. @@ -118,6 +262,10 @@ export default class CSSStyleDeclarationValueParser { * @returns Parsed value. */ public static getColor(value: string): string { + const lowerValue = value.toLowerCase(); + if (COLORS.includes(lowerValue)) { + return lowerValue; + } if (COLOR_REGEXP.test(value)) { return value; } diff --git a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts index 2131ae2e9..b8e524e2e 100644 --- a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts +++ b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts @@ -30,11 +30,24 @@ describe('CSSStyleDeclaration', () => { element.setAttribute('style', `border: 2px solid green;border-radius: 2px;font-size: 12px;`); - debugger; - expect(declaration[0]).toBe('border'); - expect(declaration[1]).toBe('border-radius'); - expect(declaration[2]).toBe('font-size'); - expect(declaration[3]).toBe(undefined); + expect(declaration[0]).toBe('border-top-width'); + expect(declaration[1]).toBe('border-right-width'); + expect(declaration[2]).toBe('border-bottom-width'); + expect(declaration[3]).toBe('border-left-width'); + expect(declaration[4]).toBe('border-top-style'); + expect(declaration[5]).toBe('border-right-style'); + expect(declaration[6]).toBe('border-bottom-style'); + expect(declaration[7]).toBe('border-left-style'); + expect(declaration[8]).toBe('border-top-color'); + expect(declaration[9]).toBe('border-right-color'); + expect(declaration[10]).toBe('border-bottom-color'); + expect(declaration[11]).toBe('border-left-color'); + expect(declaration[12]).toBe('border-top-left-radius'); + expect(declaration[13]).toBe('border-top-right-radius'); + expect(declaration[14]).toBe('border-bottom-right-radius'); + expect(declaration[15]).toBe('border-bottom-left-radius'); + expect(declaration[16]).toBe('font-size'); + expect(declaration[17]).toBe(undefined); }); it('Returns name of property without element.', () => { @@ -44,10 +57,24 @@ describe('CSSStyleDeclaration', () => { declaration.borderRadius = '2px'; declaration.fontSize = '12px'; - expect(declaration[0]).toBe('border'); - expect(declaration[1]).toBe('border-radius'); - expect(declaration[2]).toBe('font-size'); - expect(declaration[3]).toBe(undefined); + expect(declaration[0]).toBe('border-top-width'); + expect(declaration[1]).toBe('border-right-width'); + expect(declaration[2]).toBe('border-bottom-width'); + expect(declaration[3]).toBe('border-left-width'); + expect(declaration[4]).toBe('border-top-style'); + expect(declaration[5]).toBe('border-right-style'); + expect(declaration[6]).toBe('border-bottom-style'); + expect(declaration[7]).toBe('border-left-style'); + expect(declaration[8]).toBe('border-top-color'); + expect(declaration[9]).toBe('border-right-color'); + expect(declaration[10]).toBe('border-bottom-color'); + expect(declaration[11]).toBe('border-left-color'); + expect(declaration[12]).toBe('border-top-left-radius'); + expect(declaration[13]).toBe('border-top-right-radius'); + expect(declaration[14]).toBe('border-bottom-right-radius'); + expect(declaration[15]).toBe('border-bottom-left-radius'); + expect(declaration[16]).toBe('font-size'); + expect(declaration[17]).toBe(undefined); }); }); From 373819487f1a6ebce2b264692ecfa81bee0bf84d Mon Sep 17 00:00:00 2001 From: David Ortner Date: Wed, 24 Aug 2022 17:43:46 +0200 Subject: [PATCH 26/84] #344@trivial: Continue on CSSStyleDeclaration. --- .../utilities/CSSStyleDeclarationElement.ts | 63 +- ...tyleDeclarationElementDefaultProperties.ts | 484 ++++++++ ...leDeclarationElementInheritedProperties.ts | 39 + .../CSSStyleDeclarationPropertyGetParser.ts | 16 +- .../CSSStyleDeclarationPropertyManager.ts | 42 +- .../happy-dom/src/nodes/element/Element.ts | 2 +- .../happy-dom/src/nodes/element/IElement.ts | 2 +- .../src/nodes/html-element/HTMLElement.ts | 12 +- .../declaration/CSSStyleDeclaration.test.ts | 48 +- .../CSSStyleDeclarationNodeDefaultValues.ts | 1095 ----------------- .../test/nodes/element/Element.test.ts | 453 +++---- .../nodes/html-element/HTMLElement.test.ts | 63 +- .../nodes/svg-element/SVGSVGElement.test.ts | 24 +- packages/happy-dom/test/window/Window.test.ts | 8 +- 14 files changed, 879 insertions(+), 1472 deletions(-) create mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElementDefaultProperties.ts create mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElementInheritedProperties.ts delete mode 100644 packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationNodeDefaultValues.ts diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts index e68a6e21f..bfd6c9652 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts @@ -10,46 +10,8 @@ import CSSRuleTypeEnum from '../../CSSRuleTypeEnum'; import CSSMediaRule from '../../rules/CSSMediaRule'; import CSSRule from '../../CSSRule'; import CSSStyleRule from '../../rules/CSSStyleRule'; - -const INHERITED_PROPERTIES = [ - 'border-collapse', - 'border-spacing', - 'caption-side', - 'color', - 'cursor', - 'direction', - 'empty-cells', - 'font-family', - 'font-size', - 'font-style', - 'font-variant', - 'font-weight', - 'font-size-adjust', - 'font-stretch', - 'font', - 'letter-spacing', - 'line-height', - 'list-style-image', - 'list-style-position', - 'list-style-type', - 'list-style', - 'orphans', - 'quotes', - 'tab-size', - 'text-align', - 'text-align-last', - 'text-decoration-color', - 'text-indent', - 'text-justify', - 'text-shadow', - 'text-transform', - 'visibility', - 'white-space', - 'widows', - 'word-break', - 'word-spacing', - 'word-wrap' -]; +import CSSStyleDeclarationElementDefaultProperties from './CSSStyleDeclarationElementDefaultProperties'; +import CSSStyleDeclarationElementInheritedProperties from './CSSStyleDeclarationElementInheritedProperties'; /** * CSS Style Declaration utility @@ -128,9 +90,15 @@ export default class CSSStyleDeclarationElement { const propertyManager = new CSSStyleDeclarationPropertyManager( parentElement.cssText + (parentElement.element['_attributes']['style']?.value || '') ); - for (const name of Object.keys(propertyManager.properties)) { - if (INHERITED_PROPERTIES.includes(name)) { - inheritedProperties[name] = propertyManager.properties[name]; + const properties = Object.assign( + {}, + CSSStyleDeclarationElementDefaultProperties.default, + CSSStyleDeclarationElementDefaultProperties[parentElement.element.tagName], + propertyManager.properties + ); + for (const name of Object.keys(properties)) { + if (CSSStyleDeclarationElementInheritedProperties.includes(name)) { + inheritedProperties[name] = properties[name]; } } } @@ -139,8 +107,13 @@ export default class CSSStyleDeclarationElement { targetElement.cssText + (targetElement.element['_attributes']['style']?.value || '') ); - Object.assign(inheritedProperties, targetPropertyManager.properties); - targetPropertyManager.properties = inheritedProperties; + targetPropertyManager.properties = Object.assign( + {}, + CSSStyleDeclarationElementDefaultProperties.default, + CSSStyleDeclarationElementDefaultProperties[targetElement.element.tagName], + inheritedProperties, + targetPropertyManager.properties + ); return targetPropertyManager; } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElementDefaultProperties.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElementDefaultProperties.ts new file mode 100644 index 000000000..ccadd2b41 --- /dev/null +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElementDefaultProperties.ts @@ -0,0 +1,484 @@ +export default { + default: { + display: { + value: 'inline', + important: false + } + }, + A: {}, + ABBR: {}, + ADDRESS: { + display: { + value: 'block', + important: false + } + }, + AREA: {}, + ARTICLE: { + display: { + value: 'block', + important: false + } + }, + ASIDE: { + display: { + value: 'block', + important: false + } + }, + AUDIO: { + display: { + value: 'none', + important: false + } + }, + B: {}, + BASE: { + display: { + value: 'none', + important: false + } + }, + BDI: {}, + BDO: {}, + BLOCKQUAOTE: {}, + BODY: { + display: { + value: 'block', + important: false + } + }, + TEMPLATE: { + display: { + value: 'none', + important: false + } + }, + FORM: { + display: { + value: 'block', + important: false + } + }, + INPUT: { + display: { + value: 'inline-block', + important: false + } + }, + TEXTAREA: { + display: { + value: 'inline-block', + important: false + } + }, + SCRIPT: { + display: { + value: 'none', + important: false + } + }, + IMG: {}, + LINK: { + display: { + value: 'none', + important: false + } + }, + STYLE: { + display: { + value: 'none', + important: false + } + }, + LABEL: {}, + SLOT: { + display: { + value: 'contents', + important: false + } + }, + SVG: {}, + CIRCLE: {}, + ELLIPSE: {}, + LINE: {}, + PATH: {}, + POLYGON: {}, + POLYLINE: {}, + RECT: {}, + STOP: {}, + USE: {}, + META: { + display: { + value: 'none', + important: false + } + }, + BLOCKQUOTE: { + display: { + value: 'block', + important: false + } + }, + BR: {}, + BUTTON: { + display: { + value: 'inline-block', + important: false + } + }, + CANVAS: {}, + CAPTION: { + display: { + value: 'table-caption', + important: false + } + }, + CITE: {}, + CODE: {}, + COL: { + display: { + value: 'table-column', + important: false + } + }, + COLGROUP: { + display: { + value: 'table-column-group', + important: false + } + }, + DATA: {}, + DATALIST: { + display: { + value: 'none', + important: false + } + }, + DD: { + display: { + value: 'block', + important: false + } + }, + DEL: {}, + DETAILS: { + display: { + value: 'block', + important: false + } + }, + DFN: {}, + DIALOG: { + display: { + value: 'none', + important: false + } + }, + DIV: { + display: { + value: 'block', + important: false + } + }, + DL: { + display: { + value: 'block', + important: false + } + }, + DT: { + display: { + value: 'block', + important: false + } + }, + EM: {}, + EMBED: {}, + FIELDSET: { + display: { + value: 'block', + important: false + } + }, + FIGCAPTION: { + display: { + value: 'block', + important: false + } + }, + FIGURE: { + display: { + value: 'block', + important: false + } + }, + FOOTER: { + display: { + value: 'block', + important: false + } + }, + H1: { + display: { + value: 'block', + important: false + } + }, + H2: { + display: { + value: 'block', + important: false + } + }, + H3: { + display: { + value: 'block', + important: false + } + }, + H4: { + display: { + value: 'block', + important: false + } + }, + H5: { + display: { + value: 'block', + important: false + } + }, + H6: { + display: { + value: 'block', + important: false + } + }, + HEAD: { + display: { + value: 'none', + important: false + } + }, + HEADER: { + display: { + value: 'block', + important: false + } + }, + HGROUP: { + display: { + value: 'block', + important: false + } + }, + HR: { + display: { + value: 'block', + important: false + } + }, + HTML: { + display: { + value: 'block', + important: false + }, + direction: { + value: 'ltr', + important: false + } + }, + I: {}, + IFRAME: {}, + INS: {}, + KBD: {}, + LEGEND: { + display: { + value: 'block', + important: false + } + }, + LI: { + display: { + value: 'list-item', + important: false + } + }, + MAIN: { + display: { + value: 'block', + important: false + } + }, + MAP: {}, + MARK: {}, + MATH: {}, + MENU: { + display: { + value: 'block', + important: false + } + }, + MENUITEM: {}, + METER: { + display: { + value: 'inline-block', + important: false + } + }, + NAV: { + display: { + value: 'block', + important: false + } + }, + NOSCRIPT: {}, + OBJECT: {}, + OL: { + display: { + value: 'block', + important: false + } + }, + OPTGROUP: { + display: { + value: 'block', + important: false + } + }, + OPTION: { + display: { + value: 'block', + important: false + } + }, + OUTPUT: { + 'unicode-bidi': { + value: 'isolate', + important: false + } + }, + P: { + display: { + value: 'block', + important: false + } + }, + PARAM: { + display: { + value: 'none', + important: false + } + }, + PICTURE: {}, + PRE: { + display: { + value: 'block', + important: false + } + }, + PROGRESS: { + display: { + value: 'inline-block', + important: false + } + }, + Q: {}, + RB: {}, + RP: { + display: { + value: 'none', + important: false + } + }, + RT: {}, + RTC: {}, + RUBY: {}, + S: {}, + SAMP: {}, + SECTION: { + display: { + value: 'block', + important: false + } + }, + SELECT: { + display: { + value: 'inline-block', + important: false + } + }, + SMALL: {}, + SOURCE: {}, + SPAN: {}, + STRONG: {}, + SUB: {}, + SUMMARY: { + display: { + value: 'block', + important: false + } + }, + SUP: {}, + TABLE: { + display: { + value: 'table', + important: false + } + }, + TBODY: { + display: { + value: 'table-row-group', + important: false + } + }, + TD: { + display: { + value: 'table-cell', + important: false + } + }, + TFOOT: { + display: { + value: 'table-footer-group', + important: false + } + }, + TH: { + display: { + value: 'table-cell', + important: false + } + }, + THEAD: { + display: { + value: 'table-header-group', + important: false + } + }, + TIME: {}, + TITLE: { + display: { + value: 'none', + important: false + } + }, + TR: { + display: { + value: 'table-row', + important: false + } + }, + TRACK: {}, + U: {}, + UL: { + display: { + value: 'block', + important: false + } + }, + VAR: {}, + VIDEO: {}, + WBR: {} +}; diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElementInheritedProperties.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElementInheritedProperties.ts new file mode 100644 index 000000000..1b1d4f5bc --- /dev/null +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElementInheritedProperties.ts @@ -0,0 +1,39 @@ +export default [ + 'border-collapse', + 'border-spacing', + 'caption-side', + 'color', + 'cursor', + 'direction', + 'empty-cells', + 'font-family', + 'font-size', + 'font-style', + 'font-variant', + 'font-weight', + 'font-size-adjust', + 'font-stretch', + 'font', + 'letter-spacing', + 'line-height', + 'list-style-image', + 'list-style-position', + 'list-style-type', + 'list-style', + 'orphans', + 'quotes', + 'tab-size', + 'text-align', + 'text-align-last', + 'text-decoration-color', + 'text-indent', + 'text-justify', + 'text-shadow', + 'text-transform', + 'visibility', + 'white-space', + 'widows', + 'word-break', + 'word-spacing', + 'word-wrap' +]; diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts index e93d969db..1ed544904 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts @@ -23,7 +23,11 @@ export default class CSSStyleDeclarationPropertyGetParser { properties['margin-left']?.important, properties['margin-right']?.important ].some((important) => important === false), - value: `${properties['margin-top'].value} ${properties['margin-right']?.value || ''} ${ + value: `${properties['margin-top'].value} ${ + properties['margin-top'].value !== properties['margin-right']?.value + ? properties['margin-right']?.value || '' + : '' + } ${ properties['margin-top'].value !== properties['margin-bottom']?.value ? properties['margin-bottom']?.value || '' : '' @@ -56,7 +60,11 @@ export default class CSSStyleDeclarationPropertyGetParser { properties['padding-left']?.important, properties['padding-right']?.important ].some((important) => important === false), - value: `${properties['padding-top'].value} ${properties['padding-right']?.value || ''} ${ + value: `${properties['padding-top'].value} ${ + properties['padding-top'].value !== properties['padding-right']?.value + ? properties['padding-right']?.value || '' + : '' + } ${ properties['padding-top'].value !== properties['padding-bottom']?.value ? properties['padding-bottom']?.value || '' : '' @@ -324,7 +332,9 @@ export default class CSSStyleDeclarationPropertyGetParser { 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-top-right-radius']?.value + ? properties['border-top-right-radius']?.value || '' + : '' } ${ properties['border-top-left-radius'].value !== properties['border-bottom-right-radius']?.value diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts index be7a58b7c..36b6d34e0 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts @@ -10,6 +10,7 @@ export default class CSSStyleDeclarationPropertyManager { public properties: { [k: string]: ICSSStyleDeclarationPropertyValue; } = {}; + private definedPropertyNames: { [k: string]: boolean } = {}; /** * Class construtor. @@ -92,6 +93,7 @@ export default class CSSStyleDeclarationPropertyManager { */ public remove(name: string): void { delete this.properties[name]; + delete this.definedPropertyNames[name]; switch (name) { case 'border': @@ -204,6 +206,7 @@ export default class CSSStyleDeclarationPropertyManager { value: globalValue, important }; + this.definedPropertyNames[name] = true; return; } @@ -420,7 +423,10 @@ export default class CSSStyleDeclarationPropertyManager { break; } - Object.assign(this.properties, properties); + if (properties !== null && Object.keys(properties).length > 0) { + this.definedPropertyNames[name] = true; + Object.assign(this.properties, properties); + } } /** @@ -433,6 +439,7 @@ export default class CSSStyleDeclarationPropertyManager { const clone: CSSStyleDeclarationPropertyManager = new _class(); clone.properties = JSON.parse(JSON.stringify(this.properties)); + clone.definedPropertyNames = Object.assign({}, this.definedPropertyNames); return clone; } @@ -462,39 +469,10 @@ export default class CSSStyleDeclarationPropertyManager { * @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') - }; - const result = []; - for (const name of Object.keys(groupProperties)) { - if (groupProperties[name]) { - result.push( - `${name}: ${groupProperties[name].value}${ - groupProperties[name].important ? ' !important' : '' - };` - ); - clone.remove(name); - } - } - - for (const name of Object.keys(clone.properties)) { - const property = clone.properties[name]; + for (const name of Object.keys(this.definedPropertyNames)) { + const property = this.get(name); result.push(`${name}: ${property.value}${property.important ? ' !important' : ''};`); } diff --git a/packages/happy-dom/src/nodes/element/Element.ts b/packages/happy-dom/src/nodes/element/Element.ts index 7f8eed2bc..0d3809347 100644 --- a/packages/happy-dom/src/nodes/element/Element.ts +++ b/packages/happy-dom/src/nodes/element/Element.ts @@ -212,7 +212,7 @@ export default class Element extends Node implements IElement { * * @returns Attributes. */ - public get attributes(): { [k: string]: IAttr | number } { + public get attributes(): { [k: string | number]: IAttr } & { length: number } { const attributes = Object.values(this._attributes); return Object.assign({}, this._attributes, attributes, { length: attributes.length diff --git a/packages/happy-dom/src/nodes/element/IElement.ts b/packages/happy-dom/src/nodes/element/IElement.ts index 77206e4cc..cdbb41370 100644 --- a/packages/happy-dom/src/nodes/element/IElement.ts +++ b/packages/happy-dom/src/nodes/element/IElement.ts @@ -27,7 +27,7 @@ export default interface IElement extends IChildNode, INonDocumentTypeChildNode, slot: string; readonly nodeName: string; readonly localName: string; - readonly attributes: { [k: string]: IAttr | number }; + readonly attributes: { [k: string | number]: IAttr } & { length: number }; /** * Attribute changed callback. diff --git a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts index e6632bf1c..cac25a8f6 100644 --- a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts +++ b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts @@ -71,17 +71,13 @@ export default class HTMLElement extends Element implements IHTMLElement { const computedStyle = this.ownerDocument.defaultView.getComputedStyle(element); const display = computedStyle.display; - if (display === 'block') { - result += '\n'; - } + if (element.tagName !== 'SCRIPT' && element.tagName !== 'STYLE' && display !== 'none') { + if (display === 'block' && result) { + result += '\n'; + } - if (element.tagName !== 'SCRIPT' && element.tagName !== 'STYLE') { result += element.innerText; } - - if (display === 'block') { - result += '\n'; - } } else if (childNode.nodeType === NodeTypeEnum.textNode) { result += childNode.textContent.replace(/[\n\r]/, ''); } diff --git a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts index b8e524e2e..5eb70bfdb 100644 --- a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts +++ b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts @@ -120,7 +120,7 @@ describe('CSSStyleDeclaration', () => { element.setAttribute('style', `border: 2px solid green;border-radius: 2px;font-size: 12px;`); - expect(declaration.length).toBe(3); + expect(declaration.length).toBe(17); }); it('Returns length without element.', () => { @@ -130,7 +130,7 @@ describe('CSSStyleDeclaration', () => { declaration.borderRadius = '2px'; declaration.fontSize = '12px'; - expect(declaration.length).toBe(3); + expect(declaration.length).toBe(17); }); }); @@ -197,10 +197,24 @@ describe('CSSStyleDeclaration', () => { element.setAttribute('style', `border: 2px solid green;border-radius: 2px;font-size: 12px;`); - expect(declaration.item(0)).toBe('border'); - expect(declaration.item(1)).toBe('border-radius'); - expect(declaration.item(2)).toBe('font-size'); - expect(declaration.item(3)).toBe(''); + expect(declaration.item(0)).toBe('border-top-width'); + expect(declaration.item(1)).toBe('border-right-width'); + expect(declaration.item(2)).toBe('border-bottom-width'); + expect(declaration.item(3)).toBe('border-left-width'); + expect(declaration.item(4)).toBe('border-top-style'); + expect(declaration.item(5)).toBe('border-right-style'); + expect(declaration.item(6)).toBe('border-bottom-style'); + expect(declaration.item(7)).toBe('border-left-style'); + expect(declaration.item(8)).toBe('border-top-color'); + expect(declaration.item(9)).toBe('border-right-color'); + expect(declaration.item(10)).toBe('border-bottom-color'); + expect(declaration.item(11)).toBe('border-left-color'); + expect(declaration.item(12)).toBe('border-top-left-radius'); + expect(declaration.item(13)).toBe('border-top-right-radius'); + expect(declaration.item(14)).toBe('border-bottom-right-radius'); + expect(declaration.item(15)).toBe('border-bottom-left-radius'); + expect(declaration.item(16)).toBe('font-size'); + expect(declaration.item(17)).toBe(''); }); it('Returns an item by index without element.', () => { @@ -208,10 +222,24 @@ describe('CSSStyleDeclaration', () => { declaration.cssText = 'border: 2px solid green;border-radius: 2px;font-size: 12px;'; - expect(declaration.item(0)).toBe('border'); - expect(declaration.item(1)).toBe('border-radius'); - expect(declaration.item(2)).toBe('font-size'); - expect(declaration.item(3)).toBe(''); + expect(declaration.item(0)).toBe('border-top-width'); + expect(declaration.item(1)).toBe('border-right-width'); + expect(declaration.item(2)).toBe('border-bottom-width'); + expect(declaration.item(3)).toBe('border-left-width'); + expect(declaration.item(4)).toBe('border-top-style'); + expect(declaration.item(5)).toBe('border-right-style'); + expect(declaration.item(6)).toBe('border-bottom-style'); + expect(declaration.item(7)).toBe('border-left-style'); + expect(declaration.item(8)).toBe('border-top-color'); + expect(declaration.item(9)).toBe('border-right-color'); + expect(declaration.item(10)).toBe('border-bottom-color'); + expect(declaration.item(11)).toBe('border-left-color'); + expect(declaration.item(12)).toBe('border-top-left-radius'); + expect(declaration.item(13)).toBe('border-top-right-radius'); + expect(declaration.item(14)).toBe('border-bottom-right-radius'); + expect(declaration.item(15)).toBe('border-bottom-left-radius'); + expect(declaration.item(16)).toBe('font-size'); + expect(declaration.item(17)).toBe(''); }); }); diff --git a/packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationNodeDefaultValues.ts b/packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationNodeDefaultValues.ts deleted file mode 100644 index 63df45eb1..000000000 --- a/packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationNodeDefaultValues.ts +++ /dev/null @@ -1,1095 +0,0 @@ -export default { - A: {}, - ABBR: {}, - ADDRESS: { - 'block-size': '0px', - display: 'block', - 'font-style': 'italic', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - AREA: {}, - ARTICLE: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - ASIDE: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - AUDIO: { - 'block-size': '54px', - display: 'none', - height: '54px', - 'inline-size': '300px', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%', - width: '300px' - }, - B: { - 'font-weight': '700' - }, - BASE: { - display: 'none', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - BDI: { - 'unicode-bidi': 'isolate' - }, - BDO: { - 'unicode-bidi': 'bidi-override' - }, - BLOCKQUAOTE: {}, - BODY: { - 'background-color': 'rgb(255, 255, 255)', - 'block-size': '0px', - display: 'block', - 'font-size': '10.5625px', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - TEMPLATE: { - display: 'none', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - FORM: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - INPUT: { - appearance: 'auto', - 'background-color': 'rgb(255, 255, 255)', - 'block-size': '15.5px', - 'border-block-end-color': 'rgb(118, 118, 118)', - 'border-block-end-style': 'inset', - 'border-block-end-width': '2px', - 'border-block-start-color': 'rgb(118, 118, 118)', - 'border-block-start-style': 'inset', - 'border-block-start-width': '2px', - 'border-bottom-color': 'rgb(118, 118, 118)', - 'border-bottom-style': 'inset', - 'border-bottom-width': '2px', - 'border-inline-end-color': 'rgb(118, 118, 118)', - 'border-inline-end-style': 'inset', - 'border-inline-end-width': '2px', - 'border-inline-start-color': 'rgb(118, 118, 118)', - 'border-inline-start-style': 'inset', - 'border-inline-start-width': '2px', - 'border-left-color': 'rgb(118, 118, 118)', - 'border-left-style': 'inset', - 'border-left-width': '2px', - 'border-right-color': 'rgb(118, 118, 118)', - 'border-right-style': 'inset', - 'border-right-width': '2px', - 'border-top-color': 'rgb(118, 118, 118)', - 'border-top-style': 'inset', - 'border-top-width': '2px', - cursor: 'text', - display: 'inline-block', - 'font-family': 'Arial', - 'font-size': '13.3333px', - height: '15.5px', - 'inline-size': '139px', - 'padding-block-end': '1px', - 'padding-block-start': '1px', - 'padding-bottom': '1px', - 'padding-inline-end': '2px', - 'padding-inline-start': '2px', - 'padding-left': '2px', - 'padding-right': '2px', - 'padding-top': '1px', - 'perspective-origin': '73.5px 10.75px', - 'transform-origin': '73.5px 10.75px', - width: '139px' - }, - TEXTAREA: { - appearance: 'auto', - 'background-color': 'rgb(255, 255, 255)', - 'block-size': '31px', - 'border-block-end-color': 'rgb(118, 118, 118)', - 'border-block-end-style': 'solid', - 'border-block-end-width': '1px', - 'border-block-start-color': 'rgb(118, 118, 118)', - 'border-block-start-style': 'solid', - 'border-block-start-width': '1px', - 'border-bottom-color': 'rgb(118, 118, 118)', - 'border-bottom-style': 'solid', - 'border-bottom-width': '1px', - 'border-inline-end-color': 'rgb(118, 118, 118)', - 'border-inline-end-style': 'solid', - 'border-inline-end-width': '1px', - 'border-inline-start-color': 'rgb(118, 118, 118)', - 'border-inline-start-style': 'solid', - 'border-inline-start-width': '1px', - 'border-left-color': 'rgb(118, 118, 118)', - 'border-left-style': 'solid', - 'border-left-width': '1px', - 'border-right-color': 'rgb(118, 118, 118)', - 'border-right-style': 'solid', - 'border-right-width': '1px', - 'border-top-color': 'rgb(118, 118, 118)', - 'border-top-style': 'solid', - 'border-top-width': '1px', - cursor: 'text', - display: 'inline-block', - 'font-family': 'monospace', - 'font-size': '13.3333px', - height: '31px', - 'inline-size': '176px', - 'overflow-wrap': 'break-word', - 'overflow-x': 'auto', - 'overflow-y': 'auto', - 'padding-block-end': '2px', - 'padding-block-start': '2px', - 'padding-bottom': '2px', - 'padding-inline-end': '2px', - 'padding-inline-start': '2px', - 'padding-left': '2px', - 'padding-right': '2px', - 'padding-top': '2px', - 'perspective-origin': '91px 18.5px', - resize: 'both', - 'transform-origin': '91px 18.5px', - 'white-space': 'pre-wrap', - width: '176px' - }, - SCRIPT: { - display: 'none', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - IMG: { - 'block-size': '0px', - height: '0px', - 'inline-size': '0px', - width: '0px' - }, - LINK: { - display: 'none', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - STYLE: { - display: 'none', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - LABEL: { - cursor: 'default' - }, - SLOT: { - display: 'contents', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - SVG: {}, - CIRCLE: {}, - ELLIPSE: {}, - LINE: {}, - PATH: {}, - POLYGON: {}, - POLYLINE: {}, - RECT: {}, - STOP: {}, - USE: {}, - META: { - display: 'none', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - BLOCKQUOTE: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1432px', - 'margin-block-end': '13px', - 'margin-block-start': '13px', - 'margin-bottom': '13px', - 'margin-inline-end': '40px', - 'margin-inline-start': '40px', - 'margin-left': '40px', - 'margin-right': '40px', - 'margin-top': '13px', - 'perspective-origin': '716px 0px', - 'transform-origin': '716px 0px', - width: '1432px' - }, - BR: {}, - BUTTON: { - 'align-items': 'flex-start', - appearance: 'auto', - 'background-color': 'rgb(239, 239, 239)', - 'block-size': '6px', - 'border-block-end-color': 'rgb(118, 118, 118)', - 'border-block-end-style': 'outset', - 'border-block-end-width': '2px', - 'border-block-start-color': 'rgb(118, 118, 118)', - 'border-block-start-style': 'outset', - 'border-block-start-width': '2px', - 'border-bottom-color': 'rgb(118, 118, 118)', - 'border-bottom-style': 'outset', - 'border-bottom-width': '2px', - 'border-inline-end-color': 'rgb(118, 118, 118)', - 'border-inline-end-style': 'outset', - 'border-inline-end-width': '2px', - 'border-inline-start-color': 'rgb(118, 118, 118)', - 'border-inline-start-style': 'outset', - 'border-inline-start-width': '2px', - 'border-left-color': 'rgb(118, 118, 118)', - 'border-left-style': 'outset', - 'border-left-width': '2px', - 'border-right-color': 'rgb(118, 118, 118)', - 'border-right-style': 'outset', - 'border-right-width': '2px', - 'border-top-color': 'rgb(118, 118, 118)', - 'border-top-style': 'outset', - 'border-top-width': '2px', - 'box-sizing': 'border-box', - cursor: 'default', - display: 'inline-block', - 'font-size': '13.3333px', - height: '6px', - 'inline-size': '16px', - 'padding-block-end': '1px', - 'padding-block-start': '1px', - 'padding-bottom': '1px', - 'padding-inline-end': '6px', - 'padding-inline-start': '6px', - 'padding-left': '6px', - 'padding-right': '6px', - 'padding-top': '1px', - 'perspective-origin': '8px 3px', - 'text-align': 'center', - 'transform-origin': '8px 3px', - width: '16px' - }, - CANVAS: { - 'block-size': '150px', - height: '150px', - 'inline-size': '300px', - 'perspective-origin': '150px 75px', - 'transform-origin': '150px 75px', - width: '300px' - }, - CAPTION: { - 'block-size': '0px', - display: 'table-caption', - height: '0px', - 'inline-size': '0px', - 'text-align': '-webkit-center', - width: '0px' - }, - CITE: { - 'font-style': 'italic' - }, - CODE: { - 'font-family': 'monospace', - 'font-size': '10.5625px' - }, - COL: { - 'block-size': '0px', - display: 'table-column', - height: '0px', - 'inline-size': '0px', - width: '0px' - }, - COLGROUP: { - 'block-size': '0px', - display: 'table-column-group', - height: '0px', - 'inline-size': '0px', - width: '0px' - }, - DATA: {}, - DATALIST: { - display: 'none', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - DD: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1472px', - 'margin-inline-start': '40px', - 'margin-left': '40px', - 'perspective-origin': '736px 0px', - 'transform-origin': '736px 0px', - width: '1472px' - }, - DEL: { - 'text-decoration': 'line-through solid rgb(0, 0, 0)', - 'text-decoration-line': 'line-through', - '-webkit-text-decorations-in-effect': 'line-through' - }, - DETAILS: { - 'block-size': '15px', - display: 'block', - height: '15px', - 'inline-size': '1000px', - 'perspective-origin': '756px 7.5px', - 'transform-origin': '756px 7.5px', - width: '1000px' - }, - DFN: { - 'font-style': 'italic' - }, - DIALOG: { - 'background-color': 'rgb(255, 255, 255)', - 'block-size': 'fit-content', - 'border-block-end-style': 'solid', - 'border-block-end-width': '1.5px', - 'border-block-start-style': 'solid', - 'border-block-start-width': '1.5px', - 'border-bottom-style': 'solid', - 'border-bottom-width': '1.5px', - 'border-inline-end-style': 'solid', - 'border-inline-end-width': '1.5px', - 'border-inline-start-style': 'solid', - 'border-inline-start-width': '1.5px', - 'border-left-style': 'solid', - 'border-left-width': '1.5px', - 'border-right-style': 'solid', - 'border-right-width': '1.5px', - 'border-top-style': 'solid', - 'border-top-width': '1.5px', - display: 'none', - height: 'fit-content', - 'inline-size': 'fit-content', - 'inset-inline-end': '0px', - 'inset-inline-start': '0px', - left: '0px', - 'margin-block-end': 'auto', - 'margin-block-start': 'auto', - 'margin-bottom': 'auto', - 'margin-inline-end': 'auto', - 'margin-inline-start': 'auto', - 'margin-left': 'auto', - 'margin-right': 'auto', - 'margin-top': 'auto', - 'padding-block-end': '13px', - 'padding-block-start': '13px', - 'padding-bottom': '13px', - 'padding-inline-end': '13px', - 'padding-inline-start': '13px', - 'padding-left': '13px', - 'padding-right': '13px', - 'padding-top': '13px', - 'perspective-origin': '50% 50%', - position: 'absolute', - right: '0px', - 'transform-origin': '50% 50%', - width: 'fit-content' - }, - DIV: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - DL: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'margin-block-end': '13px', - 'margin-block-start': '13px', - 'margin-bottom': '13px', - 'margin-top': '13px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - DT: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - EM: { - 'font-style': 'italic' - }, - EMBED: { - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - FIELDSET: { - 'block-size': '0px', - 'border-block-end-color': 'rgb(192, 192, 192)', - 'border-block-end-style': 'groove', - 'border-block-end-width': '2px', - 'border-block-start-color': 'rgb(192, 192, 192)', - 'border-block-start-style': 'groove', - 'border-block-start-width': '2px', - 'border-bottom-color': 'rgb(192, 192, 192)', - 'border-bottom-style': 'groove', - 'border-bottom-width': '2px', - 'border-inline-end-color': 'rgb(192, 192, 192)', - 'border-inline-end-style': 'groove', - 'border-inline-end-width': '2px', - 'border-inline-start-color': 'rgb(192, 192, 192)', - 'border-inline-start-style': 'groove', - 'border-inline-start-width': '2px', - 'border-left-color': 'rgb(192, 192, 192)', - 'border-left-style': 'groove', - 'border-left-width': '2px', - 'border-right-color': 'rgb(192, 192, 192)', - 'border-right-style': 'groove', - 'border-right-width': '2px', - 'border-top-color': 'rgb(192, 192, 192)', - 'border-top-style': 'groove', - 'border-top-width': '2px', - display: 'block', - height: '0px', - 'inline-size': '1484.5px', - 'margin-inline-end': '2px', - 'margin-inline-start': '2px', - 'margin-left': '2px', - 'margin-right': '2px', - 'min-inline-size': 'min-content', - 'min-width': 'min-content', - 'padding-block-end': '8.125px', - 'padding-block-start': '4.55px', - 'padding-bottom': '8.125px', - 'padding-inline-end': '9.75px', - 'padding-inline-start': '9.75px', - 'padding-left': '9.75px', - 'padding-right': '9.75px', - 'padding-top': '4.55px', - 'perspective-origin': '754px 8.33594px', - 'transform-origin': '754px 8.33594px', - width: '1484.5px' - }, - FIGCAPTION: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - FIGURE: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1432px', - 'margin-block-end': '13px', - 'margin-block-start': '13px', - 'margin-bottom': '13px', - 'margin-inline-end': '40px', - 'margin-inline-start': '40px', - 'margin-left': '40px', - 'margin-right': '40px', - 'margin-top': '13px', - 'perspective-origin': '716px 0px', - 'transform-origin': '716px 0px', - width: '1432px' - }, - FOOTER: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - H1: { - 'block-size': '0px', - display: 'block', - 'font-size': '26px', - 'font-weight': '700', - height: '0px', - 'inline-size': '1000px', - 'margin-block-end': '17.42px', - 'margin-block-start': '17.42px', - 'margin-bottom': '17.42px', - 'margin-top': '17.42px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - H2: { - 'block-size': '0px', - display: 'block', - 'font-size': '19.5px', - 'font-weight': '700', - height: '0px', - 'inline-size': '1000px', - 'margin-block-end': '16.185px', - 'margin-block-start': '16.185px', - 'margin-bottom': '16.185px', - 'margin-top': '16.185px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - H3: { - 'block-size': '0px', - display: 'block', - 'font-size': '15.21px', - 'font-weight': '700', - height: '0px', - 'inline-size': '1000px', - 'margin-block-end': '15.21px', - 'margin-block-start': '15.21px', - 'margin-bottom': '15.21px', - 'margin-top': '15.21px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - H4: { - 'block-size': '0px', - display: 'block', - 'font-weight': '700', - height: '0px', - 'inline-size': '1000px', - 'margin-block-end': '17.29px', - 'margin-block-start': '17.29px', - 'margin-bottom': '17.29px', - 'margin-top': '17.29px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - H5: { - 'block-size': '0px', - display: 'block', - 'font-size': '10.79px', - 'font-weight': '700', - height: '0px', - 'inline-size': '1000px', - 'margin-block-end': '18.0193px', - 'margin-block-start': '18.0193px', - 'margin-bottom': '18.0193px', - 'margin-top': '18.0193px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - H6: { - 'block-size': '0px', - display: 'block', - 'font-size': '8.71px', - 'font-weight': '700', - height: '0px', - 'inline-size': '1000px', - 'margin-block-end': '20.2943px', - 'margin-block-start': '20.2943px', - 'margin-bottom': '20.2943px', - 'margin-top': '20.2943px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - HEAD: { - display: 'none', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - HEADER: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - HGROUP: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - HR: { - 'block-size': '0px', - 'border-block-end-style': 'inset', - 'border-block-end-width': '1px', - 'border-block-start-style': 'inset', - 'border-block-start-width': '1px', - 'border-bottom-style': 'inset', - 'border-bottom-width': '1px', - 'border-inline-end-style': 'inset', - 'border-inline-end-width': '1px', - 'border-inline-start-style': 'inset', - 'border-inline-start-width': '1px', - 'border-left-style': 'inset', - 'border-left-width': '1px', - 'border-right-style': 'inset', - 'border-right-width': '1px', - 'border-top-style': 'inset', - 'border-top-width': '1px', - display: 'block', - height: '0px', - 'inline-size': '1510px', - 'margin-block-end': '6.5px', - 'margin-block-start': '6.5px', - 'margin-bottom': '6.5px', - 'margin-top': '6.5px', - 'overflow-x': 'hidden', - 'overflow-y': 'hidden', - 'perspective-origin': '756px 1px', - 'transform-origin': '756px 1px', - 'unicode-bidi': 'isolate', - width: '1510px' - }, - HTML: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - I: { - 'font-style': 'italic' - }, - IFRAME: { - 'block-size': '150px', - 'border-block-end-style': 'inset', - 'border-block-end-width': '2px', - 'border-block-start-style': 'inset', - 'border-block-start-width': '2px', - 'border-bottom-style': 'inset', - 'border-bottom-width': '2px', - 'border-inline-end-style': 'inset', - 'border-inline-end-width': '2px', - 'border-inline-start-style': 'inset', - 'border-inline-start-width': '2px', - 'border-left-style': 'inset', - 'border-left-width': '2px', - 'border-right-style': 'inset', - 'border-right-width': '2px', - 'border-top-style': 'inset', - 'border-top-width': '2px', - height: '150px', - 'inline-size': '300px', - 'perspective-origin': '152px 77px', - 'transform-origin': '152px 77px', - width: '300px' - }, - INS: { - 'text-decoration': 'underline solid rgb(0, 0, 0)', - 'text-decoration-line': 'underline', - '-webkit-text-decorations-in-effect': 'underline' - }, - KBD: { - 'font-family': 'monospace', - 'font-size': '10.5625px' - }, - LEGEND: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1508px', - 'padding-inline-end': '2px', - 'padding-inline-start': '2px', - 'padding-left': '2px', - 'padding-right': '2px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1508px' - }, - LI: { - 'block-size': '15px', - display: 'list-item', - height: '15px', - 'inline-size': '1000px', - 'perspective-origin': '756px 7.5px', - 'text-align': 'left', - 'transform-origin': '756px 7.5px', - width: '1000px' - }, - MAIN: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - MAP: {}, - MARK: { - 'background-color': 'rgb(255, 255, 0)' - }, - MATH: {}, - MENU: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1472px', - 'margin-block-end': '13px', - 'margin-block-start': '13px', - 'margin-bottom': '13px', - 'margin-top': '13px', - 'padding-inline-start': '40px', - 'padding-left': '40px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1472px' - }, - MENUITEM: {}, - METER: { - appearance: 'auto', - 'block-size': '13px', - 'box-sizing': 'border-box', - display: 'inline-block', - height: '13px', - 'inline-size': '65px', - 'perspective-origin': '32.5px 6.5px', - 'transform-origin': '32.5px 6.5px', - 'vertical-align': '-2.6px', - width: '65px' - }, - NAV: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - NOSCRIPT: { - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - OBJECT: { - 'block-size': '150px', - height: '150px', - 'inline-size': '300px', - 'perspective-origin': '150px 75px', - 'transform-origin': '150px 75px', - width: '300px' - }, - OL: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1472px', - 'list-style-type': 'decimal', - 'margin-block-end': '13px', - 'margin-block-start': '13px', - 'margin-bottom': '13px', - 'margin-top': '13px', - 'padding-inline-start': '40px', - 'padding-left': '40px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1472px' - }, - OPTGROUP: { - 'block-size': '16.5938px', - display: 'block', - 'font-weight': '700', - height: '16.5938px', - 'inline-size': '1000px', - 'perspective-origin': '756px 8.29688px', - 'transform-origin': '756px 8.29688px', - width: '1000px' - }, - OPTION: { - 'block-size': '15.5938px', - display: 'block', - height: '15.5938px', - 'inline-size': '1508px', - 'min-block-size': '15.6px', - 'min-height': '15.6px', - 'padding-block-end': '1px', - 'padding-bottom': '1px', - 'padding-inline-end': '2px', - 'padding-inline-start': '2px', - 'padding-left': '2px', - 'padding-right': '2px', - 'perspective-origin': '756px 8.29688px', - 'transform-origin': '756px 8.29688px', - 'white-space': 'nowrap', - width: '1508px' - }, - OUTPUT: { - 'unicode-bidi': 'isolate' - }, - P: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'margin-block-end': '13px', - 'margin-block-start': '13px', - 'margin-bottom': '13px', - 'margin-top': '13px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - PARAM: { - display: 'none', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - PICTURE: {}, - PRE: { - 'block-size': '0px', - display: 'block', - 'font-family': 'monospace', - 'font-size': '10.5625px', - height: '0px', - 'inline-size': '1000px', - 'margin-block-end': '10.5625px', - 'margin-block-start': '10.5625px', - 'margin-bottom': '10.5625px', - 'margin-top': '10.5625px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - 'white-space': 'pre', - width: '1000px' - }, - PROGRESS: { - appearance: 'auto', - 'block-size': '13px', - 'box-sizing': 'border-box', - display: 'inline-block', - height: '13px', - 'inline-size': '130px', - 'perspective-origin': '65px 6.5px', - 'transform-origin': '65px 6.5px', - 'vertical-align': '-2.6px', - width: '130px' - }, - Q: {}, - RB: {}, - RP: { - display: 'none', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - RT: {}, - RTC: {}, - RUBY: {}, - S: { - 'text-decoration': 'line-through solid rgb(0, 0, 0)', - 'text-decoration-line': 'line-through', - '-webkit-text-decorations-in-effect': 'line-through' - }, - SAMP: { - 'font-family': 'monospace', - 'font-size': '10.5625px' - }, - SECTION: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - SELECT: { - 'align-items': 'center', - appearance: 'auto', - 'background-color': 'rgb(255, 255, 255)', - 'block-size': '19px', - 'border-block-end-color': 'rgb(118, 118, 118)', - 'border-block-end-style': 'solid', - 'border-block-end-width': '1px', - 'border-block-start-color': 'rgb(118, 118, 118)', - 'border-block-start-style': 'solid', - 'border-block-start-width': '1px', - 'border-bottom-color': 'rgb(118, 118, 118)', - 'border-bottom-style': 'solid', - 'border-bottom-width': '1px', - 'border-inline-end-color': 'rgb(118, 118, 118)', - 'border-inline-end-style': 'solid', - 'border-inline-end-width': '1px', - 'border-inline-start-color': 'rgb(118, 118, 118)', - 'border-inline-start-style': 'solid', - 'border-inline-start-width': '1px', - 'border-left-color': 'rgb(118, 118, 118)', - 'border-left-style': 'solid', - 'border-left-width': '1px', - 'border-right-color': 'rgb(118, 118, 118)', - 'border-right-style': 'solid', - 'border-right-width': '1px', - 'border-top-color': 'rgb(118, 118, 118)', - 'border-top-style': 'solid', - 'border-top-width': '1px', - 'box-sizing': 'border-box', - cursor: 'default', - display: 'inline-block', - 'font-family': 'Arial', - 'font-size': '13.3333px', - height: '19px', - 'inline-size': '22px', - 'perspective-origin': '11px 9.5px', - 'transform-origin': '11px 9.5px', - 'white-space': 'pre', - width: '22px' - }, - SMALL: { - 'font-size': '10.8333px' - }, - SOURCE: {}, - SPAN: {}, - STRONG: { - 'font-weight': '700' - }, - SUB: { - 'font-size': '10.8333px', - 'vertical-align': 'sub' - }, - SUMMARY: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1000px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1000px' - }, - SUP: { - 'font-size': '10.8333px', - 'vertical-align': 'super' - }, - TABLE: { - 'block-size': '0px', - 'border-block-end-color': 'rgb(128, 128, 128)', - 'border-block-start-color': 'rgb(128, 128, 128)', - 'border-bottom-color': 'rgb(128, 128, 128)', - 'border-inline-end-color': 'rgb(128, 128, 128)', - 'border-inline-start-color': 'rgb(128, 128, 128)', - 'border-left-color': 'rgb(128, 128, 128)', - 'border-right-color': 'rgb(128, 128, 128)', - 'border-top-color': 'rgb(128, 128, 128)', - 'box-sizing': 'border-box', - display: 'table', - height: '0px', - 'inline-size': '0px', - width: '0px', - '-webkit-border-horizontal-spacing': '2px', - '-webkit-border-vertical-spacing': '2px' - }, - TBODY: { - 'block-size': '0px', - display: 'table-row-group', - height: '0px', - 'inline-size': '0px', - 'vertical-align': 'middle', - width: '0px' - }, - TD: { - 'block-size': '0px', - display: 'table-cell', - height: '0px', - 'inline-size': '0px', - width: '0px' - }, - TFOOT: { - 'block-size': '0px', - display: 'table-footer-group', - height: '0px', - 'inline-size': '0px', - 'vertical-align': 'middle', - width: '0px' - }, - TH: { - 'block-size': '0px', - display: 'table-cell', - 'font-weight': '700', - height: '0px', - 'inline-size': '0px', - 'text-align': 'center', - width: '0px' - }, - THEAD: { - 'block-size': '0px', - display: 'table-header-group', - height: '0px', - 'inline-size': '0px', - 'vertical-align': 'middle', - width: '0px' - }, - TIME: {}, - TITLE: { - display: 'none', - 'perspective-origin': '50% 50%', - 'transform-origin': '50% 50%' - }, - TR: { - 'block-size': '0px', - display: 'table-row', - height: '0px', - 'inline-size': '0px', - width: '0px' - }, - TRACK: {}, - U: { - 'text-decoration': 'underline solid rgb(0, 0, 0)', - 'text-decoration-line': 'underline', - '-webkit-text-decorations-in-effect': 'underline' - }, - UL: { - 'block-size': '0px', - display: 'block', - height: '0px', - 'inline-size': '1472px', - 'margin-block-end': '13px', - 'margin-block-start': '13px', - 'margin-bottom': '13px', - 'margin-top': '13px', - 'padding-inline-start': '40px', - 'padding-left': '40px', - 'perspective-origin': '756px 0px', - 'transform-origin': '756px 0px', - width: '1472px' - }, - VAR: { - 'font-style': 'italic' - }, - VIDEO: { - 'block-size': '150px', - height: '150px', - 'inline-size': '300px', - 'object-fit': 'contain', - 'perspective-origin': '150px 75px', - 'transform-origin': '150px 75px', - width: '300px' - }, - WBR: {} -}; diff --git a/packages/happy-dom/test/nodes/element/Element.test.ts b/packages/happy-dom/test/nodes/element/Element.test.ts index 3f6a906bc..ee0e642c9 100644 --- a/packages/happy-dom/test/nodes/element/Element.test.ts +++ b/packages/happy-dom/test/nodes/element/Element.test.ts @@ -45,7 +45,9 @@ describe('Element', () => { element.appendChild(div1); element.appendChild(textNode); element.appendChild(div2); - expect(element.children).toEqual([div1, div2]); + expect(element.children.length).toBe(2); + expect(element.children[0] === div1).toBe(true); + expect(element.children[1] === div2).toBe(true); }); }); @@ -242,57 +244,49 @@ describe('Element', () => { element.setAttribute('key2', 'value2'); element.setAttribute('key3', 'value3'); - expect(element.attributes).toEqual({ - '0': { - name: 'key1', - value: 'value1', - namespaceURI: null, - specified: true, - ownerElement: element, - ownerDocument: document - }, - '1': { - name: 'key2', - value: 'value2', - namespaceURI: null, - specified: true, - ownerElement: element, - ownerDocument: document - }, - '2': { - name: 'key3', - value: 'value3', - namespaceURI: null, - specified: true, - ownerElement: element, - ownerDocument: document - }, - key1: { - name: 'key1', - value: 'value1', - namespaceURI: null, - specified: true, - ownerElement: element, - ownerDocument: document - }, - key2: { - name: 'key2', - value: 'value2', - namespaceURI: null, - specified: true, - ownerElement: element, - ownerDocument: document - }, - key3: { - name: 'key3', - value: 'value3', - namespaceURI: null, - specified: true, - ownerElement: element, - ownerDocument: document - }, - length: 3 - }); + expect(element.attributes.length).toBe(3); + + expect(element.attributes[0].name).toBe('key1'); + expect(element.attributes[0].value).toBe('value1'); + expect(element.attributes[0].namespaceURI).toBe(null); + expect(element.attributes[0].specified).toBe(true); + expect(element.attributes[0].ownerElement === element).toBe(true); + expect(element.attributes[0].ownerDocument === document).toBe(true); + + expect(element.attributes[1].name).toBe('key2'); + expect(element.attributes[1].value).toBe('value2'); + expect(element.attributes[1].namespaceURI).toBe(null); + expect(element.attributes[1].specified).toBe(true); + expect(element.attributes[1].ownerElement === element).toBe(true); + expect(element.attributes[1].ownerDocument === document).toBe(true); + + expect(element.attributes[2].name).toBe('key3'); + expect(element.attributes[2].value).toBe('value3'); + expect(element.attributes[2].namespaceURI).toBe(null); + expect(element.attributes[2].specified).toBe(true); + expect(element.attributes[2].ownerElement === element).toBe(true); + expect(element.attributes[2].ownerDocument === document).toBe(true); + + expect(element.attributes.key1.name).toBe('key1'); + expect(element.attributes.key1.value).toBe('value1'); + expect(element.attributes.key1.namespaceURI).toBe(null); + expect(element.attributes.key1.specified).toBe(true); + expect(element.attributes.key1.ownerElement === element).toBe(true); + expect(element.attributes.key1.ownerDocument === document).toBe(true); + + expect(element.attributes.key2.name).toBe('key2'); + expect(element.attributes.key2.value).toBe('value2'); + expect(element.attributes.key2.namespaceURI).toBe(null); + expect(element.attributes.key2.specified).toBe(true); + expect(element.attributes.key2.ownerElement === element).toBe(true); + expect(element.attributes.key2.ownerDocument === document).toBe(true); + + expect(element.attributes.key3.name).toBe('key3'); + expect(element.attributes.key3.value).toBe('value3'); + expect(element.attributes.key3.namespaceURI).toBe(null); + expect(element.attributes.key3.specified).toBe(true); + expect(element.attributes.key3.ownerElement === element).toBe(true); + expect(element.attributes.key3.ownerDocument === document).toBe(true); }); }); @@ -321,7 +315,7 @@ describe('Element', () => { div.appendChild(span2); div.appendChild(text2); - expect(div.firstElementChild).toBe(span1); + expect(div.firstElementChild === span1).toBe(true); }); }); @@ -342,7 +336,7 @@ describe('Element', () => { div.appendChild(span2); div.appendChild(text2); - expect(div.lastElementChild).toBe(span2); + expect(div.lastElementChild === span2).toBe(true); }); }); @@ -353,7 +347,7 @@ describe('Element', () => { element.appendChild(div); jest.spyOn(XMLSerializer.prototype, 'serializeToString').mockImplementation((rootElement) => { - expect(rootElement).toBe(div); + expect(rootElement === div).toBe(true); return 'EXPECTED_HTML'; }); @@ -368,7 +362,7 @@ describe('Element', () => { jest .spyOn(XMLSerializer.prototype, 'serializeToString') .mockImplementation((rootElement, options) => { - expect(rootElement).toBe(div); + expect(rootElement === div).toBe(true); expect(options).toEqual({ includeShadowRoots: true }); return 'EXPECTED_HTML'; }); @@ -384,7 +378,7 @@ describe('Element', () => { let isCalled = false; jest.spyOn(ParentNodeUtility, 'append').mockImplementation((parentNode, ...nodes) => { - expect(parentNode).toBe(document); + expect(parentNode === document).toBe(true); expect(nodes).toEqual([node1, node2]); isCalled = true; }); @@ -401,7 +395,7 @@ describe('Element', () => { let isCalled = false; jest.spyOn(ParentNodeUtility, 'prepend').mockImplementation((parentNode, ...nodes) => { - expect(parentNode).toBe(document); + expect(parentNode === document).toBe(true); expect(nodes).toEqual([node1, node2]); isCalled = true; }); @@ -420,10 +414,10 @@ describe('Element', () => { const insertedNode = parent.insertAdjacentElement('beforebegin', newNode); - expect(insertedNode).toBe(newNode); - expect(parent.childNodes).toEqual([]); + expect(insertedNode === newNode).toBe(true); + expect(parent.childNodes.length).toEqual(0); expect(insertedNode.isConnected).toBe(true); - expect(document.body.childNodes[0]).toBe(newNode); + expect(document.body.childNodes[0] === newNode).toBe(true); }); it('Returns with null if cannot insert with "beforebegin".', () => { @@ -431,7 +425,7 @@ describe('Element', () => { const newNode = document.createElement('span'); const insertedNode = parent.insertAdjacentElement('beforebegin', newNode); - expect(insertedNode).toBe(null); + expect(insertedNode === null).toBe(true); expect(newNode.isConnected).toBe(false); }); @@ -446,8 +440,8 @@ describe('Element', () => { const insertedNode = parent.insertAdjacentElement('afterbegin', newNode); - expect(insertedNode).toBe(newNode); - expect(parent.childNodes[0]).toBe(insertedNode); + expect(insertedNode === newNode).toBe(true); + expect(parent.childNodes[0] === insertedNode).toBe(true); expect(insertedNode.isConnected).toBe(true); }); @@ -461,8 +455,8 @@ describe('Element', () => { const insertedNode = parent.insertAdjacentElement('beforeend', newNode); - expect(insertedNode).toBe(newNode); - expect(parent.childNodes[1]).toBe(insertedNode); + expect(insertedNode === newNode).toBe(true); + expect(parent.childNodes[1] === insertedNode).toBe(true); expect(insertedNode.isConnected).toBe(true); }); @@ -474,12 +468,12 @@ describe('Element', () => { const insertedNode = parent.insertAdjacentElement('afterend', newNode); - expect(insertedNode).toBe(newNode); - expect(parent.childNodes).toEqual([]); + expect(insertedNode === newNode).toBe(true); + expect(parent.childNodes.length).toEqual(0); expect(insertedNode.isConnected).toBe(true); - expect(document.body.childNodes[0]).toBe(parent); - expect(document.body.childNodes[1]).toBe(insertedNode); + expect(document.body.childNodes[0] === parent).toBe(true); + expect(document.body.childNodes[1] === insertedNode).toBe(true); }); it('Inserts a Node right after the reference element and returns with it.', () => { @@ -492,13 +486,13 @@ describe('Element', () => { const insertedNode = parent.insertAdjacentElement('afterend', newNode); - expect(insertedNode).toBe(newNode); - expect(parent.childNodes).toEqual([]); + expect(insertedNode === newNode).toBe(true); + expect(parent.childNodes.length).toBe(0); expect(newNode.isConnected).toBe(true); - expect(document.body.childNodes[0]).toBe(parent); - expect(document.body.childNodes[1]).toBe(insertedNode); - expect(document.body.childNodes[2]).toBe(sibling); + expect(document.body.childNodes[0] === parent).toBe(true); + expect(document.body.childNodes[1] === insertedNode).toBe(true); + expect(document.body.childNodes[2] === sibling).toBe(true); }); it('Returns with null if cannot insert with "afterend".', () => { @@ -519,7 +513,7 @@ describe('Element', () => { document.body.appendChild(parent); parent.insertAdjacentHTML('beforebegin', markup); - expect(parent.childNodes).toEqual([]); + expect(parent.childNodes.length).toBe(0); expect((document.body.childNodes[0]).outerHTML).toEqual(markup); }); @@ -533,7 +527,7 @@ describe('Element', () => { parent.insertAdjacentHTML('afterbegin', markup); expect((parent.childNodes[0]).outerHTML).toEqual(markup); - expect(parent.childNodes[1]).toBe(child); + expect(parent.childNodes[1] === child).toBe(true); }); it('Inserts the given HTML inside the reference element after the last child.', () => { @@ -545,7 +539,7 @@ describe('Element', () => { document.body.appendChild(parent); parent.insertAdjacentHTML('beforeend', markup); - expect(parent.childNodes[0]).toBe(child); + expect(parent.childNodes[0] === child).toBe(true); expect((parent.childNodes[1]).outerHTML).toEqual(markup); }); @@ -556,8 +550,8 @@ describe('Element', () => { document.body.appendChild(parent); parent.insertAdjacentHTML('afterend', markup); - expect(parent.childNodes).toEqual([]); - expect(document.body.childNodes[0]).toBe(parent); + expect(parent.childNodes.length).toEqual(0); + expect(document.body.childNodes[0] === parent).toBe(true); expect((document.body.childNodes[1]).outerHTML).toEqual(markup); }); @@ -570,10 +564,10 @@ describe('Element', () => { document.body.appendChild(sibling); parent.insertAdjacentHTML('afterend', markup); - expect(parent.childNodes).toEqual([]); - expect(document.body.childNodes[0]).toBe(parent); + expect(parent.childNodes.length).toBe(0); + expect(document.body.childNodes[0] === parent).toBe(true); expect((document.body.childNodes[1]).outerHTML).toEqual(markup); - expect(document.body.childNodes[2]).toBe(sibling); + expect(document.body.childNodes[2] === sibling).toBe(true); }); }); @@ -585,7 +579,7 @@ describe('Element', () => { document.body.appendChild(parent); parent.insertAdjacentText('beforebegin', text); - expect(parent.childNodes).toEqual([]); + expect(parent.childNodes.length).toEqual(0); expect(document.body.childNodes[0].nodeType).toBe(Node.TEXT_NODE); expect(document.body.childNodes[0].textContent).toEqual(text); }); @@ -613,7 +607,7 @@ describe('Element', () => { document.body.appendChild(parent); parent.insertAdjacentText('beforeend', text); - expect(parent.childNodes[0]).toBe(child); + expect(parent.childNodes[0] === child).toBe(true); expect(parent.childNodes[1].nodeType).toBe(Node.TEXT_NODE); expect(parent.childNodes[1].textContent).toEqual(text); }); @@ -625,8 +619,8 @@ describe('Element', () => { document.body.appendChild(parent); parent.insertAdjacentText('afterend', text); - expect(parent.childNodes).toEqual([]); - expect(document.body.childNodes[0]).toBe(parent); + expect(parent.childNodes.length).toBe(0); + expect(document.body.childNodes[0] === parent).toBe(true); expect(document.body.childNodes[1].nodeType).toBe(Node.TEXT_NODE); expect(document.body.childNodes[1].textContent).toEqual(text); }); @@ -640,11 +634,11 @@ describe('Element', () => { document.body.appendChild(sibling); parent.insertAdjacentText('afterend', text); - expect(parent.childNodes).toEqual([]); - expect(document.body.childNodes[0]).toBe(parent); + expect(parent.childNodes.length).toBe(0); + expect(document.body.childNodes[0] === parent).toBe(true); expect(document.body.childNodes[1].nodeType).toBe(Node.TEXT_NODE); expect(document.body.childNodes[1].textContent).toEqual(text); - expect(document.body.childNodes[2]).toBe(sibling); + expect(document.body.childNodes[2] === sibling).toBe(true); }); it('Does nothing is an emptry string is sent.', () => { @@ -655,9 +649,9 @@ describe('Element', () => { document.body.appendChild(sibling); parent.insertAdjacentText('afterend', ''); - expect(parent.childNodes).toEqual([]); - expect(document.body.childNodes[0]).toBe(parent); - expect(document.body.childNodes[1]).toBe(sibling); + expect(parent.childNodes.length).toBe(0); + expect(document.body.childNodes[0] === parent).toBe(true); + expect(document.body.childNodes[1] === sibling).toBe(true); }); }); @@ -751,7 +745,9 @@ describe('Element', () => { return >[element]; }); - expect(document.querySelectorAll(expectedSelector)).toEqual([element]); + const result = document.querySelectorAll(expectedSelector); + expect(result.length).toBe(1); + expect(result[0] === element).toBe(true); }); }); @@ -766,7 +762,7 @@ describe('Element', () => { return element; }); - expect(document.querySelector(expectedSelector)).toEqual(element); + expect(document.querySelector(expectedSelector) === element).toEqual(true); }); }); @@ -783,7 +779,9 @@ describe('Element', () => { return >[child]; }); - expect(element.getElementsByClassName(className)).toEqual([child]); + const result = element.getElementsByClassName(className); + expect(result.length).toBe(1); + expect(result[0] === child).toBe(true); }); }); @@ -800,7 +798,9 @@ describe('Element', () => { return >[child]; }); - expect(element.getElementsByTagName(tagName)).toEqual([child]); + const result = element.getElementsByTagName(tagName); + expect(result.length).toBe(1); + expect(result[0] === child).toBe(true); }); }); @@ -819,7 +819,9 @@ describe('Element', () => { return >[child]; }); - expect(element.getElementsByTagNameNS(namespaceURI, tagName)).toEqual([child]); + const result = element.getElementsByTagNameNS(namespaceURI, tagName); + expect(result.length).toBe(1); + expect(result[0] === child).toBe(true); }); }); @@ -899,7 +901,9 @@ describe('Element', () => { element.appendChild(document.createComment('test')); element.appendChild(span); - expect(element.children).toEqual([div, span]); + expect(element.children.length).toBe(2); + expect(element.children[0] === div).toBe(true); + expect(element.children[1] === span).toBe(true); }); // See: https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment @@ -912,8 +916,8 @@ describe('Element', () => { element.appendChild(clone); - expect(clone.childNodes).toEqual([]); - expect(clone.children).toEqual([]); + expect(clone.childNodes.length).toBe(0); + expect(clone.children.length).toEqual(0); expect(element.innerHTML).toBe('
Div
Span'); }); }); @@ -946,7 +950,10 @@ describe('Element', () => { element.appendChild(span); element.insertBefore(div2, div1); - expect(element.children).toEqual([div2, div1, span]); + expect(element.children.length).toBe(3); + expect(element.children[0] === div2).toBe(true); + expect(element.children[1] === div2).toBe(true); + expect(element.children[2] === span).toBe(true); }); // See: https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment @@ -982,7 +989,7 @@ describe('Element', () => { return previousElementSibling; }); - expect(node.previousElementSibling).toBe(previousElementSibling); + expect(node.previousElementSibling === previousElementSibling).toBe(true); }); }); @@ -997,7 +1004,7 @@ describe('Element', () => { return nextElementSibling; }); - expect(node.nextElementSibling).toBe(nextElementSibling); + expect(node.nextElementSibling === nextElementSibling).toBe(true); }); }); @@ -1012,23 +1019,19 @@ describe('Element', () => { customElement.setAttribute('key2', 'value2'); customElement.setAttribute('KEY1', 'newValue'); - expect(customElement.changedAttributes).toEqual([ - { - name: 'key1', - newValue: 'value1', - oldValue: null - }, - { - name: 'key2', - newValue: 'value2', - oldValue: null - }, - { - name: 'key1', - newValue: 'newValue', - oldValue: 'value1' - } - ]); + expect(customElement.changedAttributes.length).toBe(3); + + expect(customElement.changedAttributes[0].name).toBe('key1'); + expect(customElement.changedAttributes[0].newValue).toBe('value1'); + expect(customElement.changedAttributes[0].oldValue).toBe(null); + + expect(customElement.changedAttributes[1].name).toBe('key2'); + expect(customElement.changedAttributes[1].newValue).toBe('value2'); + expect(customElement.changedAttributes[1].oldValue).toBe(null); + + expect(customElement.changedAttributes[2].name).toBe('key1'); + expect(customElement.changedAttributes[2].newValue).toBe('newValue'); + expect(customElement.changedAttributes[2].oldValue).toBe('value1'); }); it('Does not call the attribute changed callback when the attribute name is not available in the observedAttributes() getter method.', () => { @@ -1040,7 +1043,7 @@ describe('Element', () => { customElement.setAttribute('k1', 'value1'); customElement.setAttribute('k2', 'value2'); - expect(customElement.changedAttributes).toEqual([]); + expect(customElement.changedAttributes.length).toBe(0); }); }); @@ -1048,41 +1051,36 @@ describe('Element', () => { it('Sets an attribute on an element.', () => { element.setAttribute('key1', 'value1'); element.setAttribute('key2', ''); - expect(element.attributes).toEqual({ - '0': { - name: 'key1', - value: 'value1', - namespaceURI: null, - specified: true, - ownerElement: element, - ownerDocument: document - }, - '1': { - name: 'key2', - value: '', - namespaceURI: null, - specified: true, - ownerElement: element, - ownerDocument: document - }, - key1: { - name: 'key1', - value: 'value1', - namespaceURI: null, - specified: true, - ownerElement: element, - ownerDocument: document - }, - key2: { - name: 'key2', - value: '', - namespaceURI: null, - specified: true, - ownerElement: element, - ownerDocument: document - }, - length: 2 - }); + + expect(element.attributes.length).toBe(2); + + expect(element.attributes[0].name).toBe('key1'); + expect(element.attributes[0].value).toBe('value1'); + expect(element.attributes[0].namespaceURI).toBe(null); + expect(element.attributes[0].specified).toBe(true); + expect(element.attributes[0].ownerElement === element).toBe(true); + expect(element.attributes[0].ownerDocument === document).toBe(true); + + expect(element.attributes[1].name).toBe('key2'); + expect(element.attributes[1].value).toBe(''); + expect(element.attributes[1].namespaceURI).toBe(null); + expect(element.attributes[1].specified).toBe(true); + expect(element.attributes[1].ownerElement === element).toBe(true); + expect(element.attributes[1].ownerDocument === document).toBe(true); + + expect(element.attributes.key1.name).toBe('key1'); + expect(element.attributes.key1.value).toBe('value1'); + expect(element.attributes.key1.namespaceURI).toBe(null); + expect(element.attributes.key1.specified).toBe(true); + expect(element.attributes.key1.ownerElement === element).toBe(true); + expect(element.attributes.key1.ownerDocument === document).toBe(true); + + expect(element.attributes.key2.name).toBe('key2'); + expect(element.attributes.key2.value).toBe(''); + expect(element.attributes.key2.namespaceURI).toBe(null); + expect(element.attributes.key2.specified).toBe(true); + expect(element.attributes.key2.ownerElement === element).toBe(true); + expect(element.attributes.key2.ownerDocument === document).toBe(true); }); }); @@ -1090,41 +1088,36 @@ describe('Element', () => { it('Sets a namespace attribute on an element.', () => { element.setAttributeNS(NAMESPACE_URI, 'global:local1', 'value1'); element.setAttributeNS(NAMESPACE_URI, 'global:local2', ''); - expect(element.attributes).toEqual({ - '0': { - name: 'global:local1', - value: 'value1', - namespaceURI: NAMESPACE_URI, - specified: true, - ownerElement: element, - ownerDocument: document - }, - '1': { - name: 'global:local2', - value: '', - namespaceURI: NAMESPACE_URI, - specified: true, - ownerElement: element, - ownerDocument: document - }, - 'global:local1': { - name: 'global:local1', - value: 'value1', - namespaceURI: NAMESPACE_URI, - specified: true, - ownerElement: element, - ownerDocument: document - }, - 'global:local2': { - name: 'global:local2', - value: '', - namespaceURI: NAMESPACE_URI, - specified: true, - ownerElement: element, - ownerDocument: document - }, - length: 2 - }); + + expect(element.attributes.length).toBe(2); + + expect(element.attributes[0].name).toBe('global:local1'); + expect(element.attributes[0].value).toBe('value1'); + expect(element.attributes[0].namespaceURI).toBe(NAMESPACE_URI); + expect(element.attributes[0].specified).toBe(true); + expect(element.attributes[0].ownerElement === element).toBe(true); + expect(element.attributes[0].ownerDocument === document).toBe(true); + + expect(element.attributes[1].name).toBe('global:local2'); + expect(element.attributes[1].value).toBe(''); + expect(element.attributes[1].namespaceURI).toBe(NAMESPACE_URI); + expect(element.attributes[1].specified).toBe(true); + expect(element.attributes[1].ownerElement === element).toBe(true); + expect(element.attributes[1].ownerDocument === document).toBe(true); + + expect(element.attributes['global:local1'].name).toBe('global:local1'); + expect(element.attributes['global:local1'].value).toBe('value1'); + expect(element.attributes['global:local1'].namespaceURI).toBe(NAMESPACE_URI); + expect(element.attributes['global:local1'].specified).toBe(true); + expect(element.attributes['global:local1'].ownerElement === element).toBe(true); + expect(element.attributes['global:local1'].ownerDocument === document).toBe(true); + + expect(element.attributes['global:local2'].name).toBe('global:local2'); + expect(element.attributes['global:local2'].value).toBe(''); + expect(element.attributes['global:local2'].namespaceURI).toBe(NAMESPACE_URI); + expect(element.attributes['global:local2'].specified).toBe(true); + expect(element.attributes['global:local2'].ownerElement === element).toBe(true); + expect(element.attributes['global:local2'].ownerDocument === document).toBe(true); }); }); @@ -1188,7 +1181,7 @@ describe('Element', () => { element.attachShadow({ mode: 'open' }); expect(element['_shadowRoot'] instanceof ShadowRoot).toBe(true); expect(element.shadowRoot instanceof ShadowRoot).toBe(true); - expect(element.shadowRoot.ownerDocument).toBe(document); + expect(element.shadowRoot.ownerDocument === document).toBe(true); expect(element.shadowRoot.isConnected).toBe(false); document.appendChild(element); expect(element.shadowRoot.isConnected).toBe(true); @@ -1198,7 +1191,7 @@ describe('Element', () => { element.attachShadow({ mode: 'closed' }); expect(element.shadowRoot).toBe(null); expect(element['_shadowRoot'] instanceof ShadowRoot).toBe(true); - expect(element['_shadowRoot'].ownerDocument).toBe(document); + expect(element['_shadowRoot'].ownerDocument === document).toBe(true); expect(element['_shadowRoot'].isConnected).toBe(false); document.appendChild(element); expect(element['_shadowRoot'].isConnected).toBe(true); @@ -1284,7 +1277,7 @@ describe('Element', () => { expect(clone.scrollLeft).toBe(10); expect(clone.scrollTop).toBe(10); expect(clone.namespaceURI).toBe('namespaceURI'); - expect(clone.children).toEqual([]); + expect(clone.children.length).toEqual(0); expect(clone2.children.length).toBe(1); expect(clone2.children[0].outerHTML).toBe('
'); }); @@ -1302,6 +1295,8 @@ describe('Element', () => { element[method](attribute1); element[method](attribute2); + expect(element.attributes.length).toBe(2); + expect((element.attributes[0]).name).toBe('key1'); expect((element.attributes[0]).namespaceURI).toBe(NamespaceURI.svg); expect((element.attributes[0]).value).toBe('value1'); @@ -1329,44 +1324,6 @@ describe('Element', () => { expect((element.attributes.key2).specified).toBe(true); expect((element.attributes.key2).ownerElement).toBe(element); expect((element.attributes.key2).ownerDocument).toBe(document); - - expect(element.attributes.length).toBe(2); - - expect(element.attributes).toEqual({ - '0': { - name: 'key1', - namespaceURI: NamespaceURI.svg, - value: 'value1', - specified: true, - ownerElement: element, - ownerDocument: document - }, - '1': { - name: 'key2', - namespaceURI: null, - value: 'value2', - specified: true, - ownerElement: element, - ownerDocument: document - }, - key1: { - name: 'key1', - namespaceURI: NamespaceURI.svg, - value: 'value1', - specified: true, - ownerElement: element, - ownerDocument: document - }, - key2: { - name: 'key2', - namespaceURI: null, - value: 'value2', - specified: true, - ownerElement: element, - ownerDocument: document - }, - length: 2 - }); }); it('Sets an Attr node on an element.', () => { @@ -1380,6 +1337,8 @@ describe('Element', () => { svg[method](attribute1); svg[method](attribute2); + expect(svg.attributes.length).toBe(2); + expect((svg.attributes[0]).name).toBe('KEY1'); expect((svg.attributes[0]).namespaceURI).toBe(NamespaceURI.svg); expect((svg.attributes[0]).value).toBe('value1'); @@ -1407,8 +1366,6 @@ describe('Element', () => { expect((svg.attributes.key2).specified).toBe(true); expect((svg.attributes.key2).ownerElement).toBe(svg); expect((svg.attributes.key2).ownerDocument).toBe(document); - - expect(svg.attributes.length).toBe(2); }); }); } @@ -1424,10 +1381,10 @@ describe('Element', () => { element.setAttributeNode(attribute1); element.setAttributeNode(attribute2); - expect(element.getAttributeNode('key1')).toBe(attribute1); - expect(element.getAttributeNode('key2')).toBe(attribute2); - expect(element.getAttributeNode('KEY1')).toBe(attribute1); - expect(element.getAttributeNode('KEY2')).toBe(attribute2); + expect(element.getAttributeNode('key1') === attribute1).toBe(true); + expect(element.getAttributeNode('key2') === attribute2).toBe(true); + expect(element.getAttributeNode('KEY1') === attribute1).toBe(true); + expect(element.getAttributeNode('KEY2') === attribute2).toBe(true); }); it('Returns an Attr node from an element.', () => { @@ -1441,10 +1398,10 @@ describe('Element', () => { svg.setAttributeNode(attribute1); svg.setAttributeNode(attribute2); - expect(svg.getAttributeNode('key1')).toBe(null); - expect(svg.getAttributeNode('key2')).toBe(attribute2); - expect(svg.getAttributeNode('KEY1')).toBe(attribute1); - expect(svg.getAttributeNode('KEY2')).toBe(null); + expect(svg.getAttributeNode('key1') === null).toBe(true); + expect(svg.getAttributeNode('key2') === attribute2).toBe(true); + expect(svg.getAttributeNode('KEY1') === attribute1).toBe(true); + expect(svg.getAttributeNode('KEY2') === null).toBe(true); }); }); @@ -1456,8 +1413,8 @@ describe('Element', () => { element.setAttributeNode(attribute1); - expect(element.getAttributeNodeNS(NamespaceURI.svg, 'key1')).toBe(attribute1); - expect(element.getAttributeNodeNS(NamespaceURI.svg, 'KEY1')).toBe(attribute1); + expect(element.getAttributeNodeNS(NamespaceURI.svg, 'key1') === attribute1).toBe(true); + expect(element.getAttributeNodeNS(NamespaceURI.svg, 'KEY1') === attribute1).toBe(true); }); it('Returns an Attr node from an element.', () => { @@ -1468,9 +1425,9 @@ describe('Element', () => { svg.setAttributeNode(attribute1); - expect(svg.getAttributeNodeNS(NamespaceURI.svg, 'key1')).toBe(null); - expect(svg.getAttributeNodeNS(NamespaceURI.svg, 'KEY1')).toBe(attribute1); - expect(svg.getAttributeNodeNS(NamespaceURI.svg, 'KEY2')).toBe(null); + expect(svg.getAttributeNodeNS(NamespaceURI.svg, 'key1') === null).toBe(null); + expect(svg.getAttributeNodeNS(NamespaceURI.svg, 'KEY1') === attribute1).toBe(true); + expect(svg.getAttributeNodeNS(NamespaceURI.svg, 'KEY2') === null).toBe(true); }); }); diff --git a/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts b/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts index a85655ae7..c83258f88 100644 --- a/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts +++ b/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts @@ -83,10 +83,10 @@ describe('HTMLElement', () => { }); describe('get innerText()', () => { - it('Returns the as the textContent property if element is not connected to document, but does not return script and style content.', () => { + it('Returns the as the textContent property if element is not connected to document.', () => { const div = document.createElement('div'); const script = document.createElement('script'); - const style = document.createElement('script'); + const style = document.createElement('style'); element.appendChild(div); element.appendChild(script); element.appendChild(style); @@ -94,7 +94,7 @@ describe('HTMLElement', () => { div.appendChild(document.createTextNode('text1')); script.appendChild(document.createTextNode('var key = "value";')); style.appendChild(document.createTextNode('button { background: red; }')); - expect(element.innerText).toBe('text1text2'); + expect(element.innerText).toBe('text1var key = "value";button { background: red; }text2'); }); it('Returns the as the textContent property without any line breaks if element is not connected to document.', () => { @@ -102,8 +102,9 @@ describe('HTMLElement', () => { expect(element.innerText).toBe('The quick brown foxJumped over the lazy dog'); }); - it('Returns the as the textContent property with line breaks between block elements if element is connected to document.', () => { - element.innerHTML = `
The quick brown fox
Jumped over the lazy dog
`; + it('Returns rendered text with line breaks between block elements and without hidden elements being rendered if element is connected to the document.', () => { + element.innerHTML = `
The quick brown fox
Jumped over the lazy dog
`; + document.body.appendChild(element); expect(element.innerText).toBe('The quick brown fox\nJumped over the lazy dog'); }); }); @@ -130,17 +131,32 @@ describe('HTMLElement', () => { describe('get style()', () => { it('Returns styles.', () => { element.setAttribute('style', 'border-radius: 2px; padding: 2px;'); - expect(element.style.length).toEqual(2); - expect(element.style[0]).toEqual('border-radius'); - expect(element.style[1]).toEqual('padding'); + + expect(element.style.length).toEqual(8); + expect(element.style[0]).toEqual('border-top-left-radius'); + expect(element.style[1]).toEqual('border-top-right-radius'); + expect(element.style[2]).toEqual('border-bottom-right-radius'); + expect(element.style[3]).toEqual('border-bottom-left-radius'); + expect(element.style[4]).toEqual('padding-top'); + expect(element.style[5]).toEqual('padding-right'); + expect(element.style[6]).toEqual('padding-bottom'); + expect(element.style[7]).toEqual('padding-left'); expect(element.style.borderRadius).toEqual('2px'); expect(element.style.padding).toEqual('2px'); expect(element.style.cssText).toEqual('border-radius: 2px; padding: 2px;'); element.setAttribute('style', 'border-radius: 4px; padding: 4px;'); - expect(element.style.length).toEqual(2); - expect(element.style[0]).toEqual('border-radius'); - expect(element.style[1]).toEqual('padding'); + + expect(element.style.length).toEqual(8); + expect(element.style[0]).toEqual('border-top-left-radius'); + expect(element.style[1]).toEqual('border-top-right-radius'); + expect(element.style[2]).toEqual('border-bottom-right-radius'); + expect(element.style[3]).toEqual('border-bottom-left-radius'); + expect(element.style[4]).toEqual('padding-top'); + expect(element.style[5]).toEqual('padding-right'); + expect(element.style[6]).toEqual('padding-bottom'); + expect(element.style[7]).toEqual('padding-left'); + expect(element.style.borderRadius).toEqual('4px'); expect(element.style.padding).toEqual('4px'); expect(element.style.cssText).toEqual('border-radius: 4px; padding: 4px;'); @@ -152,10 +168,16 @@ describe('HTMLElement', () => { element.style.borderRadius = '4rem'; element.style.backgroundColor = 'green'; - expect(element.style.length).toEqual(3); - expect(element.style[0]).toEqual('border-radius'); - expect(element.style[1]).toEqual('padding'); - expect(element.style[2]).toEqual('background-color'); + expect(element.style.length).toEqual(9); + expect(element.style[0]).toEqual('border-top-left-radius'); + expect(element.style[1]).toEqual('border-top-right-radius'); + expect(element.style[2]).toEqual('border-bottom-right-radius'); + expect(element.style[3]).toEqual('border-bottom-left-radius'); + expect(element.style[4]).toEqual('padding-top'); + expect(element.style[5]).toEqual('padding-right'); + expect(element.style[6]).toEqual('padding-bottom'); + expect(element.style[7]).toEqual('padding-left'); + expect(element.style[8]).toEqual('background-color'); expect(element.style.borderRadius).toEqual('4rem'); expect(element.style.padding).toEqual('2px'); @@ -176,10 +198,13 @@ describe('HTMLElement', () => { element.style.borderRadius = ''; element.style.backgroundColor = 'green'; - expect(element.style.length).toEqual(2); - expect(element.style[0]).toEqual('padding'); - expect(element.style[1]).toEqual('background-color'); - expect(element.style[2]).toBe(undefined); + expect(element.style.length).toEqual(5); + expect(element.style[0]).toEqual('padding-top'); + expect(element.style[1]).toEqual('padding-right'); + expect(element.style[2]).toEqual('padding-bottom'); + expect(element.style[3]).toEqual('padding-left'); + expect(element.style[4]).toEqual('background-color'); + expect(element.style[5]).toBe(undefined); expect(element.style.borderRadius).toEqual(''); expect(element.style.padding).toEqual('2px'); diff --git a/packages/happy-dom/test/nodes/svg-element/SVGSVGElement.test.ts b/packages/happy-dom/test/nodes/svg-element/SVGSVGElement.test.ts index b6cc0e7bb..c6a8692f7 100644 --- a/packages/happy-dom/test/nodes/svg-element/SVGSVGElement.test.ts +++ b/packages/happy-dom/test/nodes/svg-element/SVGSVGElement.test.ts @@ -193,17 +193,29 @@ describe('SVGSVGElement', () => { describe('get style()', () => { it('Returns styles.', () => { element.setAttribute('style', 'border-radius: 2px; padding: 2px;'); - expect(element.style.length).toEqual(2); - expect(element.style[0]).toEqual('border-radius'); - expect(element.style[1]).toEqual('padding'); + expect(element.style.length).toEqual(8); + expect(element.style[0]).toEqual('border-top-left-radius'); + expect(element.style[1]).toEqual('border-top-right-radius'); + expect(element.style[2]).toEqual('border-bottom-right-radius'); + expect(element.style[3]).toEqual('border-bottom-left-radius'); + expect(element.style[4]).toEqual('padding-top'); + expect(element.style[5]).toEqual('padding-right'); + expect(element.style[6]).toEqual('padding-bottom'); + expect(element.style[7]).toEqual('padding-left'); expect(element.style.borderRadius).toEqual('2px'); expect(element.style.padding).toEqual('2px'); expect(element.style.cssText).toEqual('border-radius: 2px; padding: 2px;'); element.setAttribute('style', 'border-radius: 4px; padding: 4px;'); - expect(element.style.length).toEqual(2); - expect(element.style[0]).toEqual('border-radius'); - expect(element.style[1]).toEqual('padding'); + expect(element.style.length).toEqual(8); + expect(element.style[0]).toEqual('border-top-left-radius'); + expect(element.style[1]).toEqual('border-top-right-radius'); + expect(element.style[2]).toEqual('border-bottom-right-radius'); + expect(element.style[3]).toEqual('border-bottom-left-radius'); + expect(element.style[4]).toEqual('padding-top'); + expect(element.style[5]).toEqual('padding-right'); + expect(element.style[6]).toEqual('padding-bottom'); + expect(element.style[7]).toEqual('padding-left'); expect(element.style.borderRadius).toEqual('4px'); expect(element.style.padding).toEqual('4px'); expect(element.style.cssText).toEqual('border-radius: 4px; padding: 4px;'); diff --git a/packages/happy-dom/test/window/Window.test.ts b/packages/happy-dom/test/window/Window.test.ts index 91fcff592..7b048abb7 100644 --- a/packages/happy-dom/test/window/Window.test.ts +++ b/packages/happy-dom/test/window/Window.test.ts @@ -206,15 +206,15 @@ describe('Window', () => { const element = document.createElement('div'); const computedStyle = window.getComputedStyle(element); - element.style.direction = 'rtl'; + element.style.color = 'red'; expect(computedStyle instanceof CSSStyleDeclaration).toBe(true); - expect(computedStyle.direction).toBe(''); + expect(computedStyle.color).toBe(''); window.document.body.appendChild(element); - expect(computedStyle.direction).toBe('rtl'); + expect(computedStyle.color).toBe('red'); }); it('Returns a CSSStyleDeclaration object with computed styles from style sheets.', () => { @@ -234,7 +234,7 @@ describe('Window', () => { document.body.appendChild(documentStyle); document.body.appendChild(parent); - expect(computedStyle.font).toBe('12px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;'); + expect(computedStyle.font).toBe('12px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif'); }); }); From 4e2c3164328067dea6a18de8517bf017b04c961d Mon Sep 17 00:00:00 2001 From: David Ortner Date: Thu, 25 Aug 2022 00:47:16 +0200 Subject: [PATCH 27/84] #344@trivial: Continues on CSSStyleDeclaration. --- .../happy-dom/src/nodes/document/Document.ts | 2 +- .../test/nodes/document/Document.test.ts | 154 ++++++----- .../test/nodes/element/Element.test.ts | 14 +- .../test/xml-parser/XMLParser.test.ts | 248 +++++++++--------- 4 files changed, 230 insertions(+), 188 deletions(-) diff --git a/packages/happy-dom/src/nodes/document/Document.ts b/packages/happy-dom/src/nodes/document/Document.ts index d3be75793..a2f3d01d2 100644 --- a/packages/happy-dom/src/nodes/document/Document.ts +++ b/packages/happy-dom/src/nodes/document/Document.ts @@ -721,7 +721,7 @@ export default class Document extends Node implements IDocument { * @returns Attribute. */ public createAttribute(qualifiedName: string): IAttr { - return this.createAttributeNS(null, qualifiedName); + return this.createAttributeNS(null, qualifiedName.toLowerCase()); } /** diff --git a/packages/happy-dom/test/nodes/document/Document.test.ts b/packages/happy-dom/test/nodes/document/Document.test.ts index 78c016d9e..7dbde0617 100644 --- a/packages/happy-dom/test/nodes/document/Document.test.ts +++ b/packages/happy-dom/test/nodes/document/Document.test.ts @@ -69,7 +69,8 @@ describe('Document', () => { describe('get children()', () => { it('Returns Element child nodes.', () => { document.appendChild(document.createTextNode('test')); - expect(document.children).toEqual([document.documentElement]); + expect(document.children.length).toEqual(1); + expect(document.children[0] === document.documentElement).toBe(true); }); }); @@ -89,7 +90,11 @@ describe('Document', () => { document.body.appendChild(div); - expect(Array.from(document.scripts)).toEqual([script1, script2]); + const scripts = Array.from(document.scripts); + + expect(scripts.length).toBe(2); + expect(scripts[0]).toBe(script1); + expect(scripts[1]).toBe(script2); }); }); @@ -118,7 +123,7 @@ describe('Document', () => { div.appendChild(span2); div.appendChild(text2); - expect(div.firstElementChild).toBe(span1); + expect(div.firstElementChild === span1).toBe(true); }); }); @@ -139,7 +144,7 @@ describe('Document', () => { div.appendChild(span2); div.appendChild(text2); - expect(div.lastElementChild).toBe(span2); + expect(div.lastElementChild === span2).toBe(true); }); }); @@ -223,25 +228,25 @@ describe('Document', () => { describe('get body()', () => { it('Returns element.', () => { - expect(document.body).toBe(document.children[0].children[1]); + expect(document.body === document.children[0].children[1]).toBe(true); }); }); describe('get head()', () => { it('Returns element.', () => { - expect(document.head).toBe(document.children[0].children[0]); + expect(document.head === document.children[0].children[0]).toBe(true); }); }); describe('get documentElement()', () => { it('Returns element.', () => { - expect(document.documentElement).toBe(document.children[0]); + expect(document.documentElement === document.children[0]).toBe(true); }); }); describe('get doctype()', () => { it('Returns DocumentType element.', () => { - expect(document.doctype).toBe(document.childNodes[0]); + expect(document.doctype === document.childNodes[0]).toBe(true); }); }); @@ -314,7 +319,7 @@ describe('Document', () => { }); it('Returns the first custom element that has document as root node when the focused element is nestled in multiple shadow roots.', () => { - class CustomElementA extends window.HTMLElement { + class CustomElementA extends (window).HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); @@ -328,7 +333,7 @@ describe('Document', () => { `; } } - class CustomElementB extends window.HTMLElement { + class CustomElementB extends (window).HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); @@ -363,7 +368,7 @@ describe('Document', () => { button.focus(); button.focus(); - expect(document.activeElement).toBe(customElementA); + expect(document.activeElement === customElementA).toBe(true); expect(focusCalls).toBe(1); }); }); @@ -405,8 +410,10 @@ describe('Document', () => { let isCalled = false; jest.spyOn(ParentNodeUtility, 'append').mockImplementation((parentNode, ...nodes) => { - expect(parentNode).toBe(document); - expect(nodes).toEqual([node1, node2]); + expect(parentNode === document).toBe(true); + expect(nodes.length).toBe(2); + expect(nodes[0] === node1).toBe(true); + expect(nodes[1] === node2).toBe(true); isCalled = true; }); @@ -422,8 +429,10 @@ describe('Document', () => { let isCalled = false; jest.spyOn(ParentNodeUtility, 'prepend').mockImplementation((parentNode, ...nodes) => { - expect(parentNode).toBe(document); - expect(nodes).toEqual([node1, node2]); + expect(parentNode === document).toBe(true); + expect(nodes.length).toBe(2); + expect(nodes[0] === node1).toBe(true); + expect(nodes[1] === node2).toBe(true); isCalled = true; }); @@ -441,8 +450,10 @@ describe('Document', () => { jest .spyOn(ParentNodeUtility, 'replaceChildren') .mockImplementation((parentNode, ...nodes) => { - expect(parentNode).toBe(document); - expect(nodes).toEqual([node1, node2]); + expect(parentNode === document).toBe(true); + expect(nodes.length).toBe(2); + expect(nodes[0] === node1).toBe(true); + expect(nodes[1] === node2).toBe(true); isCalled = true; }); @@ -457,12 +468,15 @@ describe('Document', () => { const expectedSelector = 'selector'; jest.spyOn(QuerySelector, 'querySelectorAll').mockImplementation((parentNode, selector) => { - expect(parentNode).toBe(document); + expect(parentNode === document).toBe(true); expect(selector).toEqual(expectedSelector); return >[element]; }); - expect(document.querySelectorAll(expectedSelector)).toEqual([element]); + const result = document.querySelectorAll(expectedSelector); + + expect(result.length).toBe(1); + expect(result[0] === element).toBe(true); }); }); @@ -472,12 +486,12 @@ describe('Document', () => { const expectedSelector = 'selector'; jest.spyOn(QuerySelector, 'querySelector').mockImplementation((parentNode, selector) => { - expect(parentNode).toBe(document); + expect(parentNode === document).toBe(true); expect(selector).toEqual(expectedSelector); return element; }); - expect(document.querySelector(expectedSelector)).toEqual(element); + expect(document.querySelector(expectedSelector) === element).toBe(true); }); }); @@ -489,12 +503,14 @@ describe('Document', () => { jest .spyOn(ParentNodeUtility, 'getElementsByClassName') .mockImplementation((parentNode, requestedClassName) => { - expect(parentNode).toBe(document); + expect(parentNode === document).toBe(true); expect(requestedClassName).toEqual(className); return >[element]; }); - expect(document.getElementsByClassName(className)).toEqual([element]); + const result = document.getElementsByClassName(className); + expect(result.length).toBe(1); + expect(result[0] === element).toBe(true); }); }); @@ -506,12 +522,14 @@ describe('Document', () => { jest .spyOn(ParentNodeUtility, 'getElementsByTagName') .mockImplementation((parentNode, requestedTagName) => { - expect(parentNode).toBe(document); + expect(parentNode === document).toBe(true); expect(requestedTagName).toEqual(tagName); return >[element]; }); - expect(document.getElementsByTagName(tagName)).toEqual([element]); + const result = document.getElementsByTagName(tagName); + expect(result.length).toBe(1); + expect(result[0] === element).toBe(true); }); }); @@ -524,13 +542,16 @@ describe('Document', () => { jest .spyOn(ParentNodeUtility, 'getElementsByTagNameNS') .mockImplementation((parentNode, requestedNamespaceURI, requestedTagName) => { - expect(parentNode).toBe(document); + expect(parentNode === document).toBe(true); expect(requestedNamespaceURI).toEqual(namespaceURI); expect(requestedTagName).toEqual(tagName); return >[element]; }); - expect(document.getElementsByTagNameNS(namespaceURI, tagName)).toEqual([element]); + const result = document.getElementsByTagNameNS(namespaceURI, tagName); + + expect(result.length).toBe(1); + expect(result[0] === element).toBe(true); }); }); @@ -542,12 +563,12 @@ describe('Document', () => { jest .spyOn(ParentNodeUtility, 'getElementById') .mockImplementation((parentNode, requestedID) => { - expect(parentNode).toBe(document); + expect(parentNode === document).toBe(true); expect(requestedID).toEqual(id); return element; }); - expect(document.getElementById(id)).toEqual(element); + expect(document.getElementById(id) === element).toBe(true); }); }); @@ -575,7 +596,9 @@ describe('Document', () => { document.appendChild(document.createComment('test')); document.appendChild(span); - expect(document.children).toEqual([div, span]); + expect(document.children.length).toBe(2); + expect(document.children[0]).toBe(div); + expect(document.children[1]).toBe(span); }); // See: https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment @@ -592,8 +615,8 @@ describe('Document', () => { document.appendChild(clone); - expect(clone.childNodes).toEqual([]); - expect(clone.children).toEqual([]); + expect(clone.childNodes.length).toBe(0); + expect(clone.children.length).toBe(0); expect(document.children.map((child) => child.outerHTML).join('')).toBe( '
Div
Span' ); @@ -616,7 +639,8 @@ describe('Document', () => { document.removeChild(div); - expect(document.children).toEqual([span]); + expect(document.children.length).toBe(1); + expect(document.children[0]).toBe(span); }); }); @@ -636,7 +660,10 @@ describe('Document', () => { document.appendChild(span); document.insertBefore(div2, div1); - expect(document.children).toEqual([div2, div1, span]); + expect(document.children.length).toBe(3); + expect(document.children[0]).toBe(div2); + expect(document.children[1]).toBe(div1); + expect(document.children[2]).toBe(span); }); // See: https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment @@ -822,43 +849,42 @@ describe('Document', () => { describe('createAttribute()', () => { it('Creates an Attr node.', () => { const attribute = document.createAttribute('KEY1'); + expect(attribute instanceof Attr).toBe(true); - expect(attribute).toEqual({ - value: null, - name: 'key1', - namespaceURI: null, - specified: true, - ownerElement: null, - ownerDocument: document - }); + + expect(attribute.value).toBe(null); + expect(attribute.name).toBe('key1'); + expect(attribute.namespaceURI).toBe(null); + expect(attribute.specified).toBe(true); + expect(attribute.ownerElement === null).toBe(true); + expect(attribute.ownerDocument === document).toBe(true); }); }); describe('createAttributeNS()', () => { it('Creates an Attr node with namespace set to HTML.', () => { const attribute = document.createAttributeNS(NamespaceURI.html, 'KEY1'); + expect(attribute instanceof Attr).toBe(true); - expect(attribute).toEqual({ - value: null, - name: 'KEY1', - namespaceURI: NamespaceURI.html, - specified: true, - ownerElement: null, - ownerDocument: document - }); + + expect(attribute.value).toBe(null); + expect(attribute.name).toBe('KEY1'); + expect(attribute.namespaceURI).toBe(NamespaceURI.html); + expect(attribute.specified).toBe(true); + expect(attribute.ownerElement === null).toBe(true); + expect(attribute.ownerDocument === document).toBe(true); }); it('Creates an Attr node with namespace set to SVG.', () => { const attribute = document.createAttributeNS(NamespaceURI.svg, 'KEY1'); expect(attribute instanceof Attr).toBe(true); - expect(attribute).toEqual({ - value: null, - name: 'KEY1', - namespaceURI: NamespaceURI.svg, - specified: true, - ownerElement: null, - ownerDocument: document - }); + + expect(attribute.value).toBe(null); + expect(attribute.name).toBe('KEY1'); + expect(attribute.namespaceURI).toBe(NamespaceURI.svg); + expect(attribute.specified).toBe(true); + expect(attribute.ownerElement === null).toBe(true); + expect(attribute.ownerDocument === document).toBe(true); }); }); @@ -911,7 +937,7 @@ describe('Document', () => { } }; const treeWalker = document.createTreeWalker(root, whatToShow, filter); - expect(treeWalker.root).toBe(root); + expect(treeWalker.root === root).toBe(true); expect(treeWalker.whatToShow).toBe(whatToShow); expect(treeWalker.filter).toBe(filter); expect(treeWalker instanceof TreeWalker).toBe(true); @@ -945,7 +971,7 @@ describe('Document', () => { const node = new Window().document.createElement('div'); const clone = document.importNode(node); expect(clone.tagName).toBe('DIV'); - expect(clone.ownerDocument).toBe(document); + expect(clone.ownerDocument === document).toBe(true); expect(clone instanceof HTMLElement).toBe(true); }); }); @@ -963,8 +989,8 @@ describe('Document', () => { const clone = document.cloneNode(false); const clone2 = document.cloneNode(true); - expect(clone.defaultView).toBe(window); - expect(clone.children).toEqual([]); + expect(clone.defaultView === window).toBe(true); + expect(clone.children.length).toBe(0); expect(clone2.children.length).toBe(1); expect(clone2.children[0].outerHTML).toBe('
'); }); @@ -979,7 +1005,7 @@ describe('Document', () => { expect(adopted.tagName).toBe('DIV'); expect(adopted instanceof HTMLElement).toBe(true); - expect(adopted.ownerDocument).toBe(document); + expect(adopted.ownerDocument === document).toBe(true); expect(originalDocument.querySelector('div')).toBe(null); }); @@ -989,7 +1015,7 @@ describe('Document', () => { expect(adopted.tagName).toBe('DIV'); expect(adopted instanceof HTMLElement).toBe(true); - expect(adopted.ownerDocument).toBe(document); + expect(adopted.ownerDocument === document).toBe(true); }); }); diff --git a/packages/happy-dom/test/nodes/element/Element.test.ts b/packages/happy-dom/test/nodes/element/Element.test.ts index ee0e642c9..7a6e78549 100644 --- a/packages/happy-dom/test/nodes/element/Element.test.ts +++ b/packages/happy-dom/test/nodes/element/Element.test.ts @@ -379,7 +379,9 @@ describe('Element', () => { jest.spyOn(ParentNodeUtility, 'append').mockImplementation((parentNode, ...nodes) => { expect(parentNode === document).toBe(true); - expect(nodes).toEqual([node1, node2]); + expect(nodes.length).toBe(2); + expect(nodes[0] === node1).toBe(true); + expect(nodes[1] === node2).toBe(true); isCalled = true; }); @@ -396,7 +398,9 @@ describe('Element', () => { jest.spyOn(ParentNodeUtility, 'prepend').mockImplementation((parentNode, ...nodes) => { expect(parentNode === document).toBe(true); - expect(nodes).toEqual([node1, node2]); + expect(nodes.length).toBe(2); + expect(nodes[0] === node1).toBe(true); + expect(nodes[1] === node2).toBe(true); isCalled = true; }); @@ -917,7 +921,7 @@ describe('Element', () => { element.appendChild(clone); expect(clone.childNodes.length).toBe(0); - expect(clone.children.length).toEqual(0); + expect(clone.children.length).toBe(0); expect(element.innerHTML).toBe('
Div
Span'); }); }); @@ -952,7 +956,7 @@ describe('Element', () => { expect(element.children.length).toBe(3); expect(element.children[0] === div2).toBe(true); - expect(element.children[1] === div2).toBe(true); + expect(element.children[1] === div1).toBe(true); expect(element.children[2] === span).toBe(true); }); @@ -1425,7 +1429,7 @@ describe('Element', () => { svg.setAttributeNode(attribute1); - expect(svg.getAttributeNodeNS(NamespaceURI.svg, 'key1') === null).toBe(null); + expect(svg.getAttributeNodeNS(NamespaceURI.svg, 'key1') === null).toBe(true); expect(svg.getAttributeNodeNS(NamespaceURI.svg, 'KEY1') === attribute1).toBe(true); expect(svg.getAttributeNodeNS(NamespaceURI.svg, 'KEY2') === null).toBe(true); }); diff --git a/packages/happy-dom/test/xml-parser/XMLParser.test.ts b/packages/happy-dom/test/xml-parser/XMLParser.test.ts index c2ef811a8..13ed6562c 100644 --- a/packages/happy-dom/test/xml-parser/XMLParser.test.ts +++ b/packages/happy-dom/test/xml-parser/XMLParser.test.ts @@ -44,57 +44,79 @@ describe('XMLParser', () => { expect((root.childNodes[0]).tagName).toBe('DIV'); expect((root.childNodes[0]).id).toBe('id'); expect((root.childNodes[0]).className).toBe('class1 class2'); - expect((root.childNodes[0]).attributes).toEqual({ - '0': { - name: 'class', - value: 'class1 class2', - namespaceURI: null, - specified: true, - ownerElement: root.childNodes[0], - ownerDocument: document - }, - '1': { - name: 'id', - value: 'id', - namespaceURI: null, - specified: true, - ownerElement: root.childNodes[0], - ownerDocument: document - }, - '2': { - name: 'data-no-value', - value: '', - namespaceURI: null, - specified: true, - ownerElement: root.childNodes[0], - ownerDocument: document - }, - class: { - name: 'class', - value: 'class1 class2', - namespaceURI: null, - specified: true, - ownerElement: root.childNodes[0], - ownerDocument: document - }, - id: { - name: 'id', - value: 'id', - namespaceURI: null, - specified: true, - ownerElement: root.childNodes[0], - ownerDocument: document - }, - 'data-no-value': { - name: 'data-no-value', - value: '', - namespaceURI: null, - specified: true, - ownerElement: root.childNodes[0], - ownerDocument: document - }, - length: 3 - }); + + expect((root.childNodes[0]).attributes.length).toBe(3); + + expect((root.childNodes[0]).attributes[0].name).toBe('class'); + expect((root.childNodes[0]).attributes[0].value).toBe('class1 class2'); + expect((root.childNodes[0]).attributes[0].namespaceURI).toBe(null); + expect((root.childNodes[0]).attributes[0].specified).toBe(true); + expect( + (root.childNodes[0]).attributes[0].ownerElement === root.childNodes[0] + ).toBe(true); + expect((root.childNodes[0]).attributes[0].ownerDocument === document).toBe( + true + ); + + expect((root.childNodes[0]).attributes[1].name).toBe('id'); + expect((root.childNodes[0]).attributes[1].value).toBe('id'); + expect((root.childNodes[0]).attributes[1].namespaceURI).toBe(null); + expect((root.childNodes[0]).attributes[1].specified).toBe(true); + expect( + (root.childNodes[0]).attributes[1].ownerElement === root.childNodes[0] + ).toBe(true); + expect((root.childNodes[0]).attributes[1].ownerDocument === document).toBe( + true + ); + + expect((root.childNodes[0]).attributes[2].name).toBe('data-no-value'); + expect((root.childNodes[0]).attributes[2].value).toBe(''); + expect((root.childNodes[0]).attributes[2].namespaceURI).toBe(null); + expect((root.childNodes[0]).attributes[2].specified).toBe(true); + expect( + (root.childNodes[0]).attributes[2].ownerElement === root.childNodes[0] + ).toBe(true); + expect((root.childNodes[0]).attributes[2].ownerDocument === document).toBe( + true + ); + + expect((root.childNodes[0]).attributes.class.name).toBe('class'); + expect((root.childNodes[0]).attributes.class.value).toBe('class1 class2'); + expect((root.childNodes[0]).attributes.class.namespaceURI).toBe(null); + expect((root.childNodes[0]).attributes.class.specified).toBe(true); + expect( + (root.childNodes[0]).attributes.class.ownerElement === root.childNodes[0] + ).toBe(true); + expect((root.childNodes[0]).attributes.class.ownerDocument === document).toBe( + true + ); + + expect((root.childNodes[0]).attributes.id.name).toBe('id'); + expect((root.childNodes[0]).attributes.id.value).toBe('id'); + expect((root.childNodes[0]).attributes.id.namespaceURI).toBe(null); + expect((root.childNodes[0]).attributes.id.specified).toBe(true); + expect( + (root.childNodes[0]).attributes.id.ownerElement === root.childNodes[0] + ).toBe(true); + expect((root.childNodes[0]).attributes.id.ownerDocument === document).toBe( + true + ); + + expect((root.childNodes[0]).attributes['data-no-value'].name).toBe( + 'data-no-value' + ); + expect((root.childNodes[0]).attributes['data-no-value'].value).toBe(''); + expect((root.childNodes[0]).attributes['data-no-value'].namespaceURI).toBe( + null + ); + expect((root.childNodes[0]).attributes['data-no-value'].specified).toBe(true); + expect( + (root.childNodes[0]).attributes['data-no-value'].ownerElement === + root.childNodes[0] + ).toBe(true); + expect( + (root.childNodes[0]).attributes['data-no-value'].ownerDocument === document + ).toBe(true); }); it('Parses an entire HTML page.', () => { @@ -243,73 +265,63 @@ describe('XMLParser', () => { expect(circle.namespaceURI).toBe(NamespaceURI.svg); // Attributes should be in lower-case now as the namespace is HTML - expect(svg.attributes).toEqual({ - '0': { - name: 'viewBox', - value: '0 0 300 100', - namespaceURI: null, - specified: true, - ownerElement: svg, - ownerDocument: document - }, - '1': { - name: 'stroke', - value: 'red', - namespaceURI: null, - specified: true, - ownerElement: svg, - ownerDocument: document - }, - '2': { - name: 'fill', - value: 'grey', - namespaceURI: null, - specified: true, - ownerElement: svg, - ownerDocument: document - }, - '3': { - name: 'xmlns', - value: NamespaceURI.html, - namespaceURI: NamespaceURI.html, - specified: true, - ownerElement: svg, - ownerDocument: document - }, - viewBox: { - name: 'viewBox', - value: '0 0 300 100', - namespaceURI: null, - specified: true, - ownerElement: svg, - ownerDocument: document - }, - stroke: { - name: 'stroke', - value: 'red', - namespaceURI: null, - specified: true, - ownerElement: svg, - ownerDocument: document - }, - fill: { - name: 'fill', - value: 'grey', - namespaceURI: null, - specified: true, - ownerElement: svg, - ownerDocument: document - }, - xmlns: { - name: 'xmlns', - value: NamespaceURI.html, - namespaceURI: NamespaceURI.html, - specified: true, - ownerElement: svg, - ownerDocument: document - }, - length: 4 - }); + expect(svg.attributes.length).toBe(4); + + expect(svg.attributes[0].name).toBe('viewBox'); + expect(svg.attributes[0].value).toBe('0 0 300 100'); + expect(svg.attributes[0].namespaceURI).toBe(null); + expect(svg.attributes[0].specified).toBe(true); + expect(svg.attributes[0].ownerElement === svg).toBe(true); + expect(svg.attributes[0].ownerDocument === document).toBe(true); + + expect(svg.attributes[1].name).toBe('stroke'); + expect(svg.attributes[1].value).toBe('red'); + expect(svg.attributes[1].namespaceURI).toBe(null); + expect(svg.attributes[1].specified).toBe(true); + expect(svg.attributes[1].ownerElement === svg).toBe(true); + expect(svg.attributes[1].ownerDocument === document).toBe(true); + + expect(svg.attributes[2].name).toBe('fill'); + expect(svg.attributes[2].value).toBe('grey'); + expect(svg.attributes[2].namespaceURI).toBe(null); + expect(svg.attributes[2].specified).toBe(true); + expect(svg.attributes[2].ownerElement === svg).toBe(true); + expect(svg.attributes[2].ownerDocument === document).toBe(true); + + expect(svg.attributes[3].name).toBe('xmlns'); + expect(svg.attributes[3].value).toBe(NamespaceURI.html); + expect(svg.attributes[3].namespaceURI).toBe(NamespaceURI.html); + expect(svg.attributes[3].specified).toBe(true); + expect(svg.attributes[3].ownerElement === svg).toBe(true); + expect(svg.attributes[3].ownerDocument === document).toBe(true); + + expect(svg.attributes.viewBox.name).toBe('viewBox'); + expect(svg.attributes.viewBox.value).toBe('0 0 300 100'); + expect(svg.attributes.viewBox.namespaceURI).toBe(null); + expect(svg.attributes.viewBox.specified).toBe(true); + expect(svg.attributes.viewBox.ownerElement === svg).toBe(true); + expect(svg.attributes.viewBox.ownerDocument === document).toBe(true); + + expect(svg.attributes.stroke.name).toBe('stroke'); + expect(svg.attributes.stroke.value).toBe('red'); + expect(svg.attributes.stroke.namespaceURI).toBe(null); + expect(svg.attributes.stroke.specified).toBe(true); + expect(svg.attributes.stroke.ownerElement === svg).toBe(true); + expect(svg.attributes.stroke.ownerDocument === document).toBe(true); + + expect(svg.attributes.fill.name).toBe('fill'); + expect(svg.attributes.fill.value).toBe('grey'); + expect(svg.attributes.fill.namespaceURI).toBe(null); + expect(svg.attributes.fill.specified).toBe(true); + expect(svg.attributes.fill.ownerElement === svg).toBe(true); + expect(svg.attributes.fill.ownerDocument === document).toBe(true); + + expect(svg.attributes.xmlns.name).toBe('xmlns'); + expect(svg.attributes.xmlns.value).toBe(NamespaceURI.html); + expect(svg.attributes.xmlns.namespaceURI).toBe(NamespaceURI.html); + expect(svg.attributes.xmlns.specified).toBe(true); + expect(svg.attributes.xmlns.ownerElement === svg).toBe(true); + expect(svg.attributes.xmlns.ownerDocument === document).toBe(true); expect(new XMLSerializer().serializeToString(root).replace(/[\s]/gm, '')).toBe( ` From 322563bcccf9ac82d12d9892827736d1a91372c7 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Thu, 25 Aug 2022 16:55:23 +0200 Subject: [PATCH 28/84] #344@trivial: Continue on CSSStyleDeclaration. --- .../CSSStyleDeclarationPropertyGetParser.ts | 240 ++++++----- .../CSSStyleDeclarationPropertyManager.ts | 6 + .../CSSStyleDeclarationPropertySetParser.ts | 71 ++++ .../declaration/CSSStyleDeclaration.test.ts | 20 +- .../data/CSSStyleDeclarationCamelCaseKeys.ts | 371 ------------------ .../data/CSSStyleDeclarationKebabCaseKeys.ts | 371 ------------------ ...=> CSSStyleDeclarationMockedProperties.ts} | 12 + 7 files changed, 238 insertions(+), 853 deletions(-) delete mode 100644 packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationCamelCaseKeys.ts delete mode 100644 packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationKebabCaseKeys.ts rename packages/happy-dom/test/css/declaration/data/{CSSStyleDeclarationDefaultValues.ts => CSSStyleDeclarationMockedProperties.ts} (94%) diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts index 1ed544904..482cc627f 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts @@ -16,6 +16,26 @@ export default class CSSStyleDeclarationPropertyGetParser { if (!properties['margin-top']?.value) { return null; } + const values = [properties['margin-top'].value]; + if ( + properties['margin-right']?.value && + (properties['margin-right'].value !== properties['margin-top'].value || + (properties['margin-bottom']?.value && + properties['margin-bottom'].value !== properties['margin-top'].value)) + ) { + values.push(properties['margin-right'].value); + } + if ( + properties['margin-bottom']?.value && + (properties['margin-bottom'].value !== properties['margin-top'].value || + (properties['margin-left']?.value && + properties['margin-left'].value !== properties['margin-right'].value)) + ) { + values.push(properties['margin-bottom'].value); + } + if (properties['margin-left']?.value) { + values.push(properties['margin-left'].value); + } return { important: ![ properties['margin-top']?.important, @@ -23,21 +43,7 @@ export default class CSSStyleDeclarationPropertyGetParser { properties['margin-left']?.important, properties['margin-right']?.important ].some((important) => important === false), - value: `${properties['margin-top'].value} ${ - properties['margin-top'].value !== properties['margin-right']?.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() + value: values.join(' ') }; } @@ -53,6 +59,16 @@ export default class CSSStyleDeclarationPropertyGetParser { if (!properties['padding-top']?.value) { return null; } + const values = [properties['padding-top'].value]; + if (properties['padding-right']?.value) { + values.push(properties['padding-right'].value); + } + if (properties['padding-bottom']?.value) { + values.push(properties['padding-bottom'].value); + } + if (properties['padding-left']?.value) { + values.push(properties['padding-left'].value); + } return { important: ![ properties['padding-top']?.important, @@ -60,21 +76,7 @@ export default class CSSStyleDeclarationPropertyGetParser { properties['padding-left']?.important, properties['padding-right']?.important ].some((important) => important === false), - value: `${properties['padding-top'].value} ${ - properties['padding-top'].value !== properties['padding-right']?.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() + value: values.join(' ') }; } @@ -89,18 +91,34 @@ export default class CSSStyleDeclarationPropertyGetParser { }): 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 + 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; } + + const values = [properties['border-top-width'].value]; + + if ( + properties['border-top-style']?.value && + properties['border-top-style'].value === properties['border-right-style'].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 + ) { + values.push(properties['border-top-style'].value); + } + + 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 + ) { + values.push(properties['border-top-color'].value); + } + return { important: ![ properties['border-top-width']?.important, @@ -116,11 +134,7 @@ export default class CSSStyleDeclarationPropertyGetParser { 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() + value: values.join(' ') }; } @@ -136,17 +150,20 @@ export default class CSSStyleDeclarationPropertyGetParser { if (!properties['border-top-width']?.value) { return null; } + const values = [properties['border-top-width'].value]; + if (properties['border-top-style']?.value) { + values.push(properties['border-top-style'].value); + } + if (properties['border-top-color']?.value) { + values.push(properties['border-top-color'].value); + } 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() + value: values.join(' ') }; } @@ -162,17 +179,20 @@ export default class CSSStyleDeclarationPropertyGetParser { if (!properties['border-right-width']?.value) { return null; } + const values = [properties['border-right-width'].value]; + if (properties['border-right-style']?.value) { + values.push(properties['border-right-style'].value); + } + if (properties['border-right-color']?.value) { + values.push(properties['border-right-color'].value); + } 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() + value: values.join(' ') }; } @@ -188,17 +208,20 @@ export default class CSSStyleDeclarationPropertyGetParser { if (!properties['border-bottom-width']?.value) { return null; } + const values = [properties['border-bottom-width'].value]; + if (properties['border-bottom-style']?.value) { + values.push(properties['border-bottom-style'].value); + } + if (properties['border-bottom-color']?.value) { + values.push(properties['border-bottom-color'].value); + } 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() + value: values.join(' ') }; } @@ -214,17 +237,20 @@ export default class CSSStyleDeclarationPropertyGetParser { if (!properties['border-left-width']?.value) { return null; } + const values = [properties['border-left-width'].value]; + if (properties['border-left-style']?.value) { + values.push(properties['border-bottom-style'].value); + } + if (properties['border-left-color']?.value) { + values.push(properties['border-left-color'].value); + } 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() + value: values.join(' ') }; } @@ -324,6 +350,16 @@ export default class CSSStyleDeclarationPropertyGetParser { if (!properties['border-top-left-radius']?.value) { return null; } + const values = [properties['border-top-left-radius'].value]; + if (properties['border-top-right-radius']?.value) { + values.push(properties['border-top-right-radius'].value); + } + if (properties['border-bottom-right-radius']?.value) { + values.push(properties['border-bottom-right-radius'].value); + } + if (properties['border-bottom-left-radius']?.value) { + values.push(properties['border-bottom-left-radius'].value); + } return { important: ![ properties['border-top-left-radius']?.important, @@ -331,23 +367,7 @@ export default class CSSStyleDeclarationPropertyGetParser { 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-left-radius'].value !== properties['border-top-right-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() + value: values.join(' ') }; } @@ -363,6 +383,22 @@ export default class CSSStyleDeclarationPropertyGetParser { if (!properties['background-color']?.value && !properties['background-image']?.value) { return null; } + const values = []; + if (properties['background-color']?.value) { + values.push(properties['background-color'].value); + } + if (properties['background-image']?.value) { + values.push(properties['background-image'].value); + } + if (properties['background-repeat']?.value) { + values.push(properties['background-repeat'].value); + } + if (properties['background-attachment']?.value) { + values.push(properties['background-attachment'].value); + } + if (properties['background-position']?.value) { + values.push(properties['background-position'].value); + } return { important: ![ properties['background-color']?.important, @@ -371,19 +407,7 @@ export default class CSSStyleDeclarationPropertyGetParser { 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() + value: values.join(' ') }; } @@ -431,6 +455,26 @@ export default class CSSStyleDeclarationPropertyGetParser { sizeAndLineHeight.push(properties['line-height'].value); } + const values = []; + if (properties['font-style']?.value) { + values.push(properties['font-style'].value); + } + if (properties['font-variant']?.value) { + values.push(properties['font-variant'].value); + } + if (properties['font-weight']?.value) { + values.push(properties['font-weight'].value); + } + if (properties['font-stretch']?.value) { + values.push(properties['font-stretch'].value); + } + + values.push(sizeAndLineHeight.join('/')); + + if (properties['font-family']?.value) { + values.push(properties['font-family'].value); + } + return { important: ![ properties['font-style']?.important, @@ -441,13 +485,7 @@ export default class CSSStyleDeclarationPropertyGetParser { 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 || ''} ${sizeAndLineHeight.join('/')} ${ - properties['font-family'].value || '' - }` - .replace(/ /g, '') - .trim() + value: values.join(' ') }; } } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts index 36b6d34e0..eca52fddc 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts @@ -306,6 +306,12 @@ export default class CSSStyleDeclarationPropertyManager { case 'css-float': properties = CSSStyleDeclarationPropertySetParser.getCSSFloat(value, important); break; + case 'display': + properties = CSSStyleDeclarationPropertySetParser.getDisplay(value, important); + break; + case 'direction': + properties = CSSStyleDeclarationPropertySetParser.getDirection(value, important); + break; case 'float': properties = CSSStyleDeclarationPropertySetParser.getFloat(value, important); break; diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts index 02ef54cbc..6fbfb1e3f 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts @@ -49,6 +49,37 @@ const FONT_STRETCH = [ 'ultra-expanded' ]; +const DISPLAY = [ + /* Legacy values */ + 'block', + 'inline', + 'inline-block', + 'flex', + 'inline-flex', + 'grid', + 'inline-grid', + 'flow-root', + + /* Box generation */ + 'none', + 'contents', + + /* Two-value syntax */ + 'block flow', + 'inline flow', + 'inline flow-root', + 'block flex', + 'inline flex', + 'block grid', + 'inline grid', + 'block flow-root', + + /* Other values */ + 'table', + 'table-row', + 'list-item' +]; + /** * Computed style property parser. */ @@ -170,6 +201,46 @@ export default class CSSStyleDeclarationPropertySetParser { return null; } + /** + * Returns display. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getDisplay( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const lowerValue = value.toLowerCase(); + if (DISPLAY.includes(lowerValue)) { + return { display: { value: lowerValue, important } }; + } + return null; + } + + /** + * Returns direction. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getDirection( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const lowerValue = value.toLowerCase(); + if (lowerValue === 'ltr' || lowerValue === 'rtl') { + return { direction: { value: lowerValue, important } }; + } + return null; + } + /** * Returns top. * diff --git a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts index 5eb70bfdb..d74bc2c92 100644 --- a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts +++ b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts @@ -3,7 +3,7 @@ import Window from '../../../src/window/Window'; import IWindow from '../../../src/window/IWindow'; import IDocument from '../../../src/nodes/document/IDocument'; import IElement from '../../../src/nodes/element/IElement'; -import CSSStyleDeclarationDefaultValues from './data/CSSStyleDeclarationDefaultValues'; +import CSSStyleDeclarationMockedProperties from './data/CSSStyleDeclarationMockedProperties'; function KEBAB_TO_CAMEL_CASE(text: string): string { const parts = text.split('-'); @@ -78,38 +78,38 @@ describe('CSSStyleDeclaration', () => { }); }); - for (const property of Object.keys(CSSStyleDeclarationDefaultValues)) { + for (const property of Object.keys(CSSStyleDeclarationMockedProperties)) { const camelCaseProperty = KEBAB_TO_CAMEL_CASE(property); describe(`get ${camelCaseProperty}()`, () => { it('Returns style property on element.', () => { const declaration = new CSSStyleDeclaration(element); element.setAttribute( 'style', - `${property}: ${CSSStyleDeclarationDefaultValues[property]};` + `${property}: ${CSSStyleDeclarationMockedProperties[property]};` ); - expect(declaration[camelCaseProperty]).toBe(CSSStyleDeclarationDefaultValues[property]); + expect(declaration[camelCaseProperty]).toBe(CSSStyleDeclarationMockedProperties[property]); }); it('Returns style property without element.', () => { const declaration = new CSSStyleDeclaration(); - declaration[camelCaseProperty] = CSSStyleDeclarationDefaultValues[property]; - expect(declaration[camelCaseProperty]).toBe(CSSStyleDeclarationDefaultValues[property]); + declaration[camelCaseProperty] = CSSStyleDeclarationMockedProperties[property]; + expect(declaration[camelCaseProperty]).toBe(CSSStyleDeclarationMockedProperties[property]); }); }); describe(`set ${camelCaseProperty}()`, () => { it('Sets style property on element.', () => { const declaration = new CSSStyleDeclaration(element); - declaration[camelCaseProperty] = CSSStyleDeclarationDefaultValues[property]; + declaration[camelCaseProperty] = CSSStyleDeclarationMockedProperties[property]; expect(element.getAttribute('style')).toBe( - `${property}: ${CSSStyleDeclarationDefaultValues[property]};` + `${property}: ${CSSStyleDeclarationMockedProperties[property]};` ); }); it('Sets style property without element.', () => { const declaration = new CSSStyleDeclaration(); - declaration[camelCaseProperty] = CSSStyleDeclarationDefaultValues[property]; - expect(declaration[camelCaseProperty]).toBe(CSSStyleDeclarationDefaultValues[property]); + declaration[camelCaseProperty] = CSSStyleDeclarationMockedProperties[property]; + expect(declaration[camelCaseProperty]).toBe(CSSStyleDeclarationMockedProperties[property]); }); }); } diff --git a/packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationCamelCaseKeys.ts b/packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationCamelCaseKeys.ts deleted file mode 100644 index 60e73c3ae..000000000 --- a/packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationCamelCaseKeys.ts +++ /dev/null @@ -1,371 +0,0 @@ -export default [ - 'alignContent', - 'alignItems', - 'alignSelf', - 'alignmentBaseline', - 'all', - 'animation', - 'animationDelay', - 'animationDirection', - 'animationDuration', - 'animationFillMode', - 'animationIterationCount', - 'animationName', - 'animationPlayState', - 'animationTimingFunction', - 'appearance', - 'backdropFilter', - 'backfaceVisibility', - 'background', - 'backgroundAttachment', - 'backgroundBlendMode', - 'backgroundClip', - 'backgroundColor', - 'backgroundImage', - 'backgroundOrigin', - 'backgroundPosition', - 'backgroundPositionX', - 'backgroundPositionY', - 'backgroundRepeat', - 'backgroundRepeatX', - 'backgroundRepeatY', - 'backgroundSize', - 'baselineShift', - 'blockSize', - 'border', - 'borderBlockEnd', - 'borderBlockEndColor', - 'borderBlockEndStyle', - 'borderBlockEndWidth', - 'borderBlockStart', - 'borderBlockStartColor', - 'borderBlockStartStyle', - 'borderBlockStartWidth', - 'borderBottom', - 'borderBottomColor', - 'borderBottomLeftRadius', - 'borderBottomRightRadius', - 'borderBottomStyle', - 'borderBottomWidth', - 'borderCollapse', - 'borderColor', - 'borderImage', - 'borderImageOutset', - 'borderImageRepeat', - 'borderImageSlice', - 'borderImageSource', - 'borderImageWidth', - 'borderInlineEnd', - 'borderInlineEndColor', - 'borderInlineEndStyle', - 'borderInlineEndWidth', - 'borderInlineStart', - 'borderInlineStartColor', - 'borderInlineStartStyle', - 'borderInlineStartWidth', - 'borderLeft', - 'borderLeftColor', - 'borderLeftStyle', - 'borderLeftWidth', - 'borderRadius', - 'borderRight', - 'borderRightColor', - 'borderRightStyle', - 'borderRightWidth', - 'borderSpacing', - 'borderStyle', - 'borderTop', - 'borderTopColor', - 'borderTopLeftRadius', - 'borderTopRightRadius', - 'borderTopStyle', - 'borderTopWidth', - 'borderWidth', - 'bottom', - 'boxShadow', - 'boxSizing', - 'breakAfter', - 'breakBefore', - 'breakInside', - 'bufferedRendering', - 'captionSide', - 'caretColor', - 'clear', - 'clip', - 'clipPath', - 'clipRule', - 'color', - 'colorInterpolation', - 'colorInterpolationFilters', - 'colorRendering', - 'colorScheme', - 'columnCount', - 'columnFill', - 'columnGap', - 'columnRule', - 'columnRuleColor', - 'columnRuleStyle', - 'columnRuleWidth', - 'columnSpan', - 'columnWidth', - 'columns', - 'contain', - 'containIntrinsicSize', - 'content', - 'contentVisibility', - 'counterIncrement', - 'counterReset', - 'counterSet', - 'cssFloat', - 'cursor', - 'cx', - 'cy', - 'd', - 'direction', - 'display', - 'dominantBaseline', - 'emptyCells', - 'fill', - 'fillOpacity', - 'fillRule', - 'filter', - 'flex', - 'flexBasis', - 'flexDirection', - 'flexFlow', - 'flexGrow', - 'flexShrink', - 'flexWrap', - 'float', - 'floodColor', - 'floodOpacity', - 'font', - 'fontDisplay', - 'fontFamily', - 'fontFeatureSettings', - 'fontKerning', - 'fontOpticalSizing', - 'fontSize', - 'fontStretch', - 'fontStyle', - 'fontVariant', - 'fontVariantCaps', - 'fontVariantEastAsian', - 'fontVariantLigatures', - 'fontVariantNumeric', - 'fontVariationSettings', - 'fontWeight', - 'gap', - 'grid', - 'gridArea', - 'gridAutoColumns', - 'gridAutoFlow', - 'gridAutoRows', - 'gridColumn', - 'gridColumnEnd', - 'gridColumnGap', - 'gridColumnStart', - 'gridGap', - 'gridRow', - 'gridRowEnd', - 'gridRowGap', - 'gridRowStart', - 'gridTemplate', - 'gridTemplateAreas', - 'gridTemplateColumns', - 'gridTemplateRows', - 'height', - 'hyphens', - 'imageOrientation', - 'imageRendering', - 'inherits', - 'initialValue', - 'inlineSize', - 'isolation', - 'justifyContent', - 'justifyItems', - 'justifySelf', - 'left', - 'letterSpacing', - 'lightingColor', - 'lineBreak', - 'lineHeight', - 'listStyle', - 'listStyleImage', - 'listStylePosition', - 'listStyleType', - 'margin', - 'marginBlockEnd', - 'marginBlockStart', - 'marginBottom', - 'marginInlineEnd', - 'marginInlineStart', - 'marginLeft', - 'marginRight', - 'marginTop', - 'marker', - 'markerEnd', - 'markerMid', - 'markerStart', - 'mask', - 'maskType', - 'maxBlockSize', - 'maxHeight', - 'maxInlineSize', - 'maxWidth', - 'maxZoom', - 'minBlockSize', - 'minHeight', - 'minInlineSize', - 'minWidth', - 'minZoom', - 'mixBlendMode', - 'objectFit', - 'objectPosition', - 'offset', - 'offsetDistance', - 'offsetPath', - 'offsetRotate', - 'opacity', - 'order', - 'orientation', - 'orphans', - 'outline', - 'outlineColor', - 'outlineOffset', - 'outlineStyle', - 'outlineWidth', - 'overflow', - 'overflowAnchor', - 'overflowWrap', - 'overflowX', - 'overflowY', - 'overscrollBehavior', - 'overscrollBehaviorBlock', - 'overscrollBehaviorInline', - 'overscrollBehaviorX', - 'overscrollBehaviorY', - 'padding', - 'paddingBlockEnd', - 'paddingBlockStart', - 'paddingBottom', - 'paddingInlineEnd', - 'paddingInlineStart', - 'paddingLeft', - 'paddingRight', - 'paddingTop', - 'page', - 'pageBreakAfter', - 'pageBreakBefore', - 'pageBreakInside', - 'pageOrientation', - 'paintOrder', - 'perspective', - 'perspectiveOrigin', - 'placeContent', - 'placeItems', - 'placeSelf', - 'pointerEvents', - 'position', - 'quotes', - 'r', - 'resize', - 'right', - 'rowGap', - 'rubyPosition', - 'rx', - 'ry', - 'scrollBehavior', - 'scrollMargin', - 'scrollMarginBlock', - 'scrollMarginBlockEnd', - 'scrollMarginBlockStart', - 'scrollMarginBottom', - 'scrollMarginInline', - 'scrollMarginInlineEnd', - 'scrollMarginInlineStart', - 'scrollMarginLeft', - 'scrollMarginRight', - 'scrollMarginTop', - 'scrollPadding', - 'scrollPaddingBlock', - 'scrollPaddingBlockEnd', - 'scrollPaddingBlockStart', - 'scrollPaddingBottom', - 'scrollPaddingInline', - 'scrollPaddingInlineEnd', - 'scrollPaddingInlineStart', - 'scrollPaddingLeft', - 'scrollPaddingRight', - 'scrollPaddingTop', - 'scrollSnapAlign', - 'scrollSnapStop', - 'scrollSnapType', - 'shapeImageThreshold', - 'shapeMargin', - 'shapeOutside', - 'shapeRendering', - 'size', - 'speak', - 'src', - 'stopColor', - 'stopOpacity', - 'stroke', - 'strokeDasharray', - 'strokeDashoffset', - 'strokeLinecap', - 'strokeLinejoin', - 'strokeMiterlimit', - 'strokeOpacity', - 'strokeWidth', - 'syntax', - 'tabSize', - 'tableLayout', - 'textAlign', - 'textAlignLast', - 'textAnchor', - 'textCombineUpright', - 'textDecoration', - 'textDecorationColor', - 'textDecorationLine', - 'textDecorationSkipInk', - 'textDecorationStyle', - 'textIndent', - 'textOrientation', - 'textOverflow', - 'textRendering', - 'textShadow', - 'textSizeAdjust', - 'textTransform', - 'textUnderlinePosition', - 'top', - 'touchAction', - 'transform', - 'transformBox', - 'transformOrigin', - 'transformStyle', - 'transition', - 'transitionDelay', - 'transitionDuration', - 'transitionProperty', - 'transitionTimingFunction', - 'unicodeBidi', - 'unicodeRange', - 'userSelect', - 'userZoom', - 'vectorEffect', - 'verticalAlign', - 'visibility', - 'whiteSpace', - 'widows', - 'width', - 'willChange', - 'wordBreak', - 'wordSpacing', - 'wordWrap', - 'writingMode', - 'x', - 'y', - 'zIndex', - 'zoom' -]; diff --git a/packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationKebabCaseKeys.ts b/packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationKebabCaseKeys.ts deleted file mode 100644 index c6fa9f9c0..000000000 --- a/packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationKebabCaseKeys.ts +++ /dev/null @@ -1,371 +0,0 @@ -export default [ - 'align-content', - 'align-items', - 'align-self', - 'alignment-baseline', - 'all', - 'animation', - 'animation-delay', - 'animation-direction', - 'animation-duration', - 'animation-fill-mode', - 'animation-iteration-count', - 'animation-name', - 'animation-play-state', - 'animation-timing-function', - 'appearance', - 'backdrop-filter', - 'backface-visibility', - 'background', - 'background-attachment', - 'background-blend-mode', - 'background-clip', - 'background-color', - 'background-image', - 'background-origin', - 'background-position', - 'background-position-x', - 'background-position-y', - 'background-repeat', - 'background-repeat-x', - 'background-repeat-y', - 'background-size', - 'baseline-shift', - 'block-size', - 'border', - 'border-block-end', - 'border-block-end-color', - 'border-block-end-style', - 'border-block-end-width', - 'border-block-start', - 'border-block-start-color', - 'border-block-start-style', - 'border-block-start-width', - 'border-bottom', - 'border-bottom-color', - 'border-bottom-left-radius', - 'border-bottom-right-radius', - 'border-bottom-style', - 'border-bottom-width', - 'border-collapse', - 'border-color', - 'border-image', - 'border-image-outset', - 'border-image-repeat', - 'border-image-slice', - 'border-image-source', - 'border-image-width', - 'border-inline-end', - 'border-inline-end-color', - 'border-inline-end-style', - 'border-inline-end-width', - 'border-inline-start', - 'border-inline-start-color', - 'border-inline-start-style', - 'border-inline-start-width', - 'border-left', - 'border-left-color', - 'border-left-style', - 'border-left-width', - 'border-radius', - 'border-right', - 'border-right-color', - 'border-right-style', - 'border-right-width', - 'border-spacing', - 'border-style', - 'border-top', - 'border-top-color', - 'border-top-left-radius', - 'border-top-right-radius', - 'border-top-style', - 'border-top-width', - 'border-width', - 'bottom', - 'box-shadow', - 'box-sizing', - 'break-after', - 'break-before', - 'break-inside', - 'buffered-rendering', - 'caption-side', - 'caret-color', - 'clear', - 'clip', - 'clip-path', - 'clip-rule', - 'color', - 'color-interpolation', - 'color-interpolation-filters', - 'color-rendering', - 'color-scheme', - 'column-count', - 'column-fill', - 'column-gap', - 'column-rule', - 'column-rule-color', - 'column-rule-style', - 'column-rule-width', - 'column-span', - 'column-width', - 'columns', - 'contain', - 'contain-intrinsic-size', - 'content', - 'content-visibility', - 'counter-increment', - 'counter-reset', - 'counter-set', - 'css-float', - 'cursor', - 'cx', - 'cy', - 'd', - 'direction', - 'display', - 'dominant-baseline', - 'empty-cells', - 'fill', - 'fill-opacity', - 'fill-rule', - 'filter', - 'flex', - 'flex-basis', - 'flex-direction', - 'flex-flow', - 'flex-grow', - 'flex-shrink', - 'flex-wrap', - 'float', - 'flood-color', - 'flood-opacity', - 'font', - 'font-display', - 'font-family', - 'font-feature-settings', - 'font-kerning', - 'font-optical-sizing', - 'font-size', - 'font-stretch', - 'font-style', - 'font-variant', - 'font-variant-caps', - 'font-variant-east-asian', - 'font-variant-ligatures', - 'font-variant-numeric', - 'font-variation-settings', - 'font-weight', - 'gap', - 'grid', - 'grid-area', - 'grid-auto-columns', - 'grid-auto-flow', - 'grid-auto-rows', - 'grid-column', - 'grid-column-end', - 'grid-column-gap', - 'grid-column-start', - 'grid-gap', - 'grid-row', - 'grid-row-end', - 'grid-row-gap', - 'grid-row-start', - 'grid-template', - 'grid-template-areas', - 'grid-template-columns', - 'grid-template-rows', - 'height', - 'hyphens', - 'image-orientation', - 'image-rendering', - 'inherits', - 'initial-value', - 'inline-size', - 'isolation', - 'justify-content', - 'justify-items', - 'justify-self', - 'left', - 'letter-spacing', - 'lighting-color', - 'line-break', - 'line-height', - 'list-style', - 'list-style-image', - 'list-style-position', - 'list-style-type', - 'margin', - 'margin-block-end', - 'margin-block-start', - 'margin-bottom', - 'margin-inline-end', - 'margin-inline-start', - 'margin-left', - 'margin-right', - 'margin-top', - 'marker', - 'marker-end', - 'marker-mid', - 'marker-start', - 'mask', - 'mask-type', - 'max-block-size', - 'max-height', - 'max-inline-size', - 'max-width', - 'max-zoom', - 'min-block-size', - 'min-height', - 'min-inline-size', - 'min-width', - 'min-zoom', - 'mix-blend-mode', - 'object-fit', - 'object-position', - 'offset', - 'offset-distance', - 'offset-path', - 'offset-rotate', - 'opacity', - 'order', - 'orientation', - 'orphans', - 'outline', - 'outline-color', - 'outline-offset', - 'outline-style', - 'outline-width', - 'overflow', - 'overflow-anchor', - 'overflow-wrap', - 'overflow-x', - 'overflow-y', - 'overscroll-behavior', - 'overscroll-behavior-block', - 'overscroll-behavior-inline', - 'overscroll-behavior-x', - 'overscroll-behavior-y', - 'padding', - 'padding-block-end', - 'padding-block-start', - 'padding-bottom', - 'padding-inline-end', - 'padding-inline-start', - 'padding-left', - 'padding-right', - 'padding-top', - 'page', - 'page-break-after', - 'page-break-before', - 'page-break-inside', - 'page-orientation', - 'paint-order', - 'perspective', - 'perspective-origin', - 'place-content', - 'place-items', - 'place-self', - 'pointer-events', - 'position', - 'quotes', - 'r', - 'resize', - 'right', - 'row-gap', - 'ruby-position', - 'rx', - 'ry', - 'scroll-behavior', - 'scroll-margin', - 'scroll-margin-block', - 'scroll-margin-block-end', - 'scroll-margin-block-start', - 'scroll-margin-bottom', - 'scroll-margin-inline', - 'scroll-margin-inline-end', - 'scroll-margin-inline-start', - 'scroll-margin-left', - 'scroll-margin-right', - 'scroll-margin-top', - 'scroll-padding', - 'scroll-padding-block', - 'scroll-padding-block-end', - 'scroll-padding-block-start', - 'scroll-padding-bottom', - 'scroll-padding-inline', - 'scroll-padding-inline-end', - 'scroll-padding-inline-start', - 'scroll-padding-left', - 'scroll-padding-right', - 'scroll-padding-top', - 'scroll-snap-align', - 'scroll-snap-stop', - 'scroll-snap-type', - 'shape-image-threshold', - 'shape-margin', - 'shape-outside', - 'shape-rendering', - 'size', - 'speak', - 'src', - 'stop-color', - 'stop-opacity', - 'stroke', - 'stroke-dasharray', - 'stroke-dashoffset', - 'stroke-linecap', - 'stroke-linejoin', - 'stroke-miterlimit', - 'stroke-opacity', - 'stroke-width', - 'syntax', - 'tab-size', - 'table-layout', - 'text-align', - 'text-align-last', - 'text-anchor', - 'text-combine-upright', - 'text-decoration', - 'text-decoration-color', - 'text-decoration-line', - 'text-decoration-skip-ink', - 'text-decoration-style', - 'text-indent', - 'text-orientation', - 'text-overflow', - 'text-rendering', - 'text-shadow', - 'text-size-adjust', - 'text-transform', - 'text-underline-position', - 'top', - 'touch-action', - 'transform', - 'transform-box', - 'transform-origin', - 'transform-style', - 'transition', - 'transition-delay', - 'transition-duration', - 'transition-property', - 'transition-timing-function', - 'unicode-bidi', - 'unicode-range', - 'user-select', - 'user-zoom', - 'vector-effect', - 'vertical-align', - 'visibility', - 'white-space', - 'widows', - 'width', - 'will-change', - 'word-break', - 'word-spacing', - 'word-wrap', - 'writing-mode', - 'x', - 'y', - 'z-index', - 'zoom' -]; diff --git a/packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationDefaultValues.ts b/packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationMockedProperties.ts similarity index 94% rename from packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationDefaultValues.ts rename to packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationMockedProperties.ts index 335e05b30..f3651cd11 100644 --- a/packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationDefaultValues.ts +++ b/packages/happy-dom/test/css/declaration/data/CSSStyleDeclarationMockedProperties.ts @@ -16,6 +16,8 @@ export default { appearance: 'none', 'backdrop-filter': 'none', 'backface-visibility': 'visible', + background: + 'center / contain no-repeat url("../../media/examples/firefox-logo.svg"), #eee 35% url("../../media/examples/lizard.png")', 'background-attachment': 'scroll', 'background-blend-mode': 'normal', 'background-clip': 'border-box', @@ -27,6 +29,11 @@ export default { 'background-size': 'auto', 'baseline-shift': '0px', 'block-size': 'auto', + border: '1rem solid red', + 'border-top': '1rem solid red', + 'border-right': '1rem solid red', + 'border-bottom': '1rem solid red', + 'border-left': '1rem solid red', 'border-block-end-color': 'rgb(0, 0, 0)', 'border-block-end-style': 'none', 'border-block-end-width': '0px', @@ -65,6 +72,7 @@ export default { 'border-top-right-radius': '0px', 'border-top-style': 'none', 'border-top-width': '0px', + 'border-radius': '1px 2px 3px 4px', bottom: 'auto', 'box-shadow': 'none', 'box-sizing': 'content-box', @@ -107,6 +115,7 @@ export default { 'fill-opacity': '1', 'fill-rule': 'nonzero', filter: 'none', + flex: '1 2 fill', 'flex-basis': 'auto', 'flex-direction': 'row', 'flex-grow': '0', @@ -115,6 +124,7 @@ export default { float: 'none', 'flood-color': 'rgb(0, 0, 0)', 'flood-opacity': '1', + font: 'italic small-caps bold 2rem/2.2 cursive', 'font-family': 'Roboto, system-ui, sans-serif', 'font-kerning': 'auto', 'font-optical-sizing': 'auto', @@ -162,6 +172,7 @@ export default { 'list-style-image': 'none', 'list-style-position': 'outside', 'list-style-type': 'disc', + margin: '1px 2px 3px 4px', 'margin-block-end': '0px', 'margin-block-start': '0px', 'margin-bottom': '0px', @@ -202,6 +213,7 @@ export default { 'overflow-y': 'visible', 'overscroll-behavior-block': 'auto', 'overscroll-behavior-inline': 'auto', + padding: '1px 2px 3px 4px', 'padding-block-end': '0px', 'padding-block-start': '0px', 'padding-bottom': '0px', From fb13f370b1586dbe781a5e105bbc934a75fa1def Mon Sep 17 00:00:00 2001 From: David Ortner Date: Wed, 31 Aug 2022 16:47:21 +0200 Subject: [PATCH 29/84] #344@trivial: Continue on CSSStyleDeclaration. --- .../CSSStyleDeclarationPropertyGetParser.ts | 141 ++++++++---------- .../declaration/CSSStyleDeclaration.test.ts | 84 +++++------ 2 files changed, 107 insertions(+), 118 deletions(-) diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts index 482cc627f..25f623f14 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts @@ -13,38 +13,12 @@ export default class CSSStyleDeclarationPropertyGetParser { public static getMargin(properties: { [k: string]: ICSSStyleDeclarationPropertyValue; }): ICSSStyleDeclarationPropertyValue { - if (!properties['margin-top']?.value) { - return null; - } - const values = [properties['margin-top'].value]; - if ( - properties['margin-right']?.value && - (properties['margin-right'].value !== properties['margin-top'].value || - (properties['margin-bottom']?.value && - properties['margin-bottom'].value !== properties['margin-top'].value)) - ) { - values.push(properties['margin-right'].value); - } - if ( - properties['margin-bottom']?.value && - (properties['margin-bottom'].value !== properties['margin-top'].value || - (properties['margin-left']?.value && - properties['margin-left'].value !== properties['margin-right'].value)) - ) { - values.push(properties['margin-bottom'].value); - } - if (properties['margin-left']?.value) { - values.push(properties['margin-left'].value); - } - return { - important: ![ - properties['margin-top']?.important, - properties['margin-bottom']?.important, - properties['margin-left']?.important, - properties['margin-right']?.important - ].some((important) => important === false), - value: values.join(' ') - }; + return this.getPositionedValue( + properties['margin-top'], + properties['margin-right'], + properties['margin-bottom'], + properties['margin-left'] + ); } /** @@ -56,28 +30,12 @@ export default class CSSStyleDeclarationPropertyGetParser { public static getPadding(properties: { [k: string]: ICSSStyleDeclarationPropertyValue; }): ICSSStyleDeclarationPropertyValue { - if (!properties['padding-top']?.value) { - return null; - } - const values = [properties['padding-top'].value]; - if (properties['padding-right']?.value) { - values.push(properties['padding-right'].value); - } - if (properties['padding-bottom']?.value) { - values.push(properties['padding-bottom'].value); - } - if (properties['padding-left']?.value) { - values.push(properties['padding-left'].value); - } - return { - important: ![ - properties['padding-top']?.important, - properties['padding-bottom']?.important, - properties['padding-left']?.important, - properties['padding-right']?.important - ].some((important) => important === false), - value: values.join(' ') - }; + return this.getPositionedValue( + properties['padding-top'], + properties['padding-right'], + properties['padding-bottom'], + properties['padding-left'] + ); } /** @@ -347,28 +305,12 @@ export default class CSSStyleDeclarationPropertyGetParser { public static getBorderRadius(properties: { [k: string]: ICSSStyleDeclarationPropertyValue; }): ICSSStyleDeclarationPropertyValue { - if (!properties['border-top-left-radius']?.value) { - return null; - } - const values = [properties['border-top-left-radius'].value]; - if (properties['border-top-right-radius']?.value) { - values.push(properties['border-top-right-radius'].value); - } - if (properties['border-bottom-right-radius']?.value) { - values.push(properties['border-bottom-right-radius'].value); - } - if (properties['border-bottom-left-radius']?.value) { - values.push(properties['border-bottom-left-radius'].value); - } - 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: values.join(' ') - }; + return this.getPositionedValue( + properties['border-top-left-radius'], + properties['border-top-right-radius'], + properties['border-bottom-right-radius'], + properties['border-bottom-left-radius'] + ); } /** @@ -488,4 +430,51 @@ export default class CSSStyleDeclarationPropertyGetParser { value: values.join(' ') }; } + + /** + * Returns a positioned value. + * + * @param top Top. + * @param right Right + * @param bottom Bottom. + * @param left Left. + * @returns Property value + */ + public static getPositionedValue( + top?: ICSSStyleDeclarationPropertyValue, + right?: ICSSStyleDeclarationPropertyValue, + bottom?: ICSSStyleDeclarationPropertyValue, + left?: ICSSStyleDeclarationPropertyValue + ): ICSSStyleDeclarationPropertyValue { + if (!top?.value) { + return null; + } + + const values = [top.value]; + + if (right?.value && right.value !== top.value) { + values.push(right.value); + } + + if (bottom?.value && bottom.value !== top.value) { + for (let i = values.length - 1; i < 1; i++) { + values.push('0px'); + } + values.push(bottom.value); + } + + if (left?.value && left.value !== right?.value) { + for (let i = values.length - 1; i < 2; i++) { + values.push('0px'); + } + values.push(left.value); + } + + return { + important: ![top?.important, right?.important, bottom?.important, left?.important].some( + (important) => important === false + ), + value: values.join(' ') + }; + } } diff --git a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts index d74bc2c92..a3091fd96 100644 --- a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts +++ b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts @@ -3,15 +3,6 @@ import Window from '../../../src/window/Window'; import IWindow from '../../../src/window/IWindow'; import IDocument from '../../../src/nodes/document/IDocument'; import IElement from '../../../src/nodes/element/IElement'; -import CSSStyleDeclarationMockedProperties from './data/CSSStyleDeclarationMockedProperties'; - -function KEBAB_TO_CAMEL_CASE(text: string): string { - const parts = text.split('-'); - for (let i = 0, max = parts.length; i < max; i++) { - parts[i] = i > 0 ? parts[i].charAt(0).toUpperCase() + parts[i].slice(1) : parts[i]; - } - return parts.join(''); -} describe('CSSStyleDeclaration', () => { let window: IWindow; @@ -78,41 +69,50 @@ describe('CSSStyleDeclaration', () => { }); }); - for (const property of Object.keys(CSSStyleDeclarationMockedProperties)) { - const camelCaseProperty = KEBAB_TO_CAMEL_CASE(property); - describe(`get ${camelCaseProperty}()`, () => { - it('Returns style property on element.', () => { - const declaration = new CSSStyleDeclaration(element); - element.setAttribute( - 'style', - `${property}: ${CSSStyleDeclarationMockedProperties[property]};` - ); - expect(declaration[camelCaseProperty]).toBe(CSSStyleDeclarationMockedProperties[property]); - }); - - it('Returns style property without element.', () => { - const declaration = new CSSStyleDeclaration(); - declaration[camelCaseProperty] = CSSStyleDeclarationMockedProperties[property]; - expect(declaration[camelCaseProperty]).toBe(CSSStyleDeclarationMockedProperties[property]); - }); - }); + describe('get border()', () => { + it('Returns style property on element.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border: 2px solid green'); + + expect(declaration.border).toBe('2px solid green'); + + expect(declaration.borderTop).toBe('2px solid green'); + expect(declaration.borderRight).toBe('2px solid green'); + expect(declaration.borderBottom).toBe('2px solid green'); + expect(declaration.borderLeft).toBe('2px solid green'); + + expect(declaration.borderTopColor).toBe('green'); + expect(declaration.borderTopWidth).toBe('2px'); + expect(declaration.borderTopStyle).toBe('solid'); + + expect(declaration.borderRightColor).toBe('green'); + expect(declaration.borderRightWidth).toBe('2px'); + expect(declaration.borderRightStyle).toBe('solid'); - describe(`set ${camelCaseProperty}()`, () => { - it('Sets style property on element.', () => { - const declaration = new CSSStyleDeclaration(element); - declaration[camelCaseProperty] = CSSStyleDeclarationMockedProperties[property]; - expect(element.getAttribute('style')).toBe( - `${property}: ${CSSStyleDeclarationMockedProperties[property]};` - ); - }); - - it('Sets style property without element.', () => { - const declaration = new CSSStyleDeclaration(); - declaration[camelCaseProperty] = CSSStyleDeclarationMockedProperties[property]; - expect(declaration[camelCaseProperty]).toBe(CSSStyleDeclarationMockedProperties[property]); - }); + expect(declaration.borderBottomColor).toBe('green'); + expect(declaration.borderBottomWidth).toBe('2px'); + expect(declaration.borderBottomStyle).toBe('solid'); + + expect(declaration.borderLeftColor).toBe('green'); + expect(declaration.borderLeftWidth).toBe('2px'); + expect(declaration.borderLeftStyle).toBe('solid'); + + declaration.borderRight = '1px dotted red'; + + expect(declaration.border).toBe(''); + + declaration.borderRight = '2px solid green'; + + expect(declaration.border).toBe('2px solid green'); + + declaration.borderColor = 'red'; + declaration.borderStyle = 'dotted'; + declaration.borderWidth = '1px'; + + expect(declaration.border).toBe('1px dotted red'); }); - } + }); describe('get length()', () => { it('Returns length when of styles on element.', () => { From 4ca836be649cbf8faa463381bfa60cfd8350012a Mon Sep 17 00:00:00 2001 From: David Ortner Date: Thu, 1 Sep 2022 00:57:05 +0200 Subject: [PATCH 30/84] #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. * From 36575ffdaa2e2b4b3850bc25d7bcd9e906298f96 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Thu, 1 Sep 2022 16:48:34 +0200 Subject: [PATCH 31/84] #344@trivial: Continue on CSSStyleDeclaration. --- .../CSSStyleDeclarationPropertySetParser.ts | 505 +++++++++++------- 1 file changed, 303 insertions(+), 202 deletions(-) diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts index 6852cc1b7..c15d35cf0 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts @@ -372,12 +372,7 @@ export default class CSSStyleDeclarationPropertySetParser { [key: string]: ICSSStyleDeclarationPropertyValue; } { const lowerValue = value.toLowerCase(); - if ( - CSSStyleDeclarationValueParser.getGlobal(lowerValue) || - lowerValue === 'auto' || - lowerValue === 'initial' || - lowerValue === 'inherit' - ) { + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || lowerValue === 'auto') { return { clip: { value: lowerValue, important } }; } const matches = lowerValue.match(RECT_REGEXP); @@ -453,21 +448,16 @@ export default class CSSStyleDeclarationPropertySetParser { 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); - } - - if (borderStyle) { - Object.assign(properties, borderStyle); - } - if (borderColor) { - Object.assign(properties, borderColor); + if (borderWidth && borderStyle !== null && borderColor !== null) { + return { + ...this.getBorderImage('initial', important), + ...borderWidth, + ...borderStyle, + ...borderColor + }; } - - return borderWidth && borderStyle !== null && borderColor !== null ? properties : null; + return null; } /** @@ -483,24 +473,13 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const lowerValue = value.toLowerCase(); - if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || BORDER_WIDTH.includes(lowerValue)) { - return { - 'border-top-width': { value: lowerValue, important }, - 'border-right-width': { value: lowerValue, important }, - 'border-bottom-width': { value: lowerValue, important }, - 'border-left-width': { value: lowerValue, important } - }; - } - const length = CSSStyleDeclarationValueParser.getLength(value); - return length - ? { - 'border-top-width': { value: length, important }, - 'border-right-width': { value: length, important }, - 'border-bottom-width': { value: length, important }, - 'border-left-width': { value: length, important } - } - : null; + const properties = { + ...this.getBorderTopWidth(value, important), + ...this.getBorderRightWidth(value, important), + ...this.getBorderBottomWidth(value, important), + ...this.getBorderLeftWidth(value, important) + }; + return Object.keys(properties).length ? properties : null; } /** * Returns border style. @@ -515,16 +494,13 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const lowerValue = value.toLowerCase(); - if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || BORDER_STYLE.includes(lowerValue)) { - return { - 'border-top-style': { value: lowerValue, important }, - 'border-right-style': { value: lowerValue, important }, - 'border-bottom-style': { value: lowerValue, important }, - 'border-left-style': { value: lowerValue, important } - }; - } - return null; + const properties = { + ...this.getBorderTopStyle(value, important), + ...this.getBorderRightStyle(value, important), + ...this.getBorderBottomStyle(value, important), + ...this.getBorderLeftStyle(value, important) + }; + return Object.keys(properties).length ? properties : null; } /** @@ -540,17 +516,13 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const color = - CSSStyleDeclarationValueParser.getGlobal(value) || - CSSStyleDeclarationValueParser.getColor(value); - return color - ? { - 'border-top-color': { value: color, important }, - 'border-right-color': { value: color, important }, - 'border-bottom-color': { value: color, important }, - 'border-left-color': { value: color, important } - } - : null; + const properties = { + ...this.getBorderTopStyle(value, important), + ...this.getBorderRightStyle(value, important), + ...this.getBorderBottomStyle(value, important), + ...this.getBorderLeftStyle(value, important) + }; + return Object.keys(properties).length ? properties : null; } /** @@ -604,35 +576,23 @@ export default class CSSStyleDeclarationPropertySetParser { ? 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 && + if ( + borderImageSource && borderImageRepeat !== null && borderImageSlice !== null && borderImageWidth !== null && borderImageOutset !== null - ? properties - : null; + ) { + return { + ...borderImageSource, + ...borderImageSlice, + ...borderImageWidth, + ...borderImageOutset, + ...borderImageRepeat + }; + } + + return null; } /** @@ -853,8 +813,17 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const borderWidth = this.getBorderWidth(value, important); - return borderWidth ? { 'border-top-width': borderWidth['border-top-width'] } : null; + const lowerValue = value.toLowerCase(); + const parsedValue = + BORDER_WIDTH.includes(lowerValue) || CSSStyleDeclarationValueParser.getGlobal(lowerValue) + ? lowerValue + : CSSStyleDeclarationValueParser.getLength(value); + if (parsedValue) { + return { + 'border-top-width': { value: parsedValue, important } + }; + } + return null; } /** @@ -870,8 +839,17 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const borderWidth = this.getBorderWidth(value, important); - return borderWidth ? { 'border-right-width': borderWidth['border-right-width'] } : null; + const lowerValue = value.toLowerCase(); + const parsedValue = + BORDER_WIDTH.includes(lowerValue) || CSSStyleDeclarationValueParser.getGlobal(lowerValue) + ? lowerValue + : CSSStyleDeclarationValueParser.getLength(value); + if (parsedValue) { + return { + 'border-right-width': { value: parsedValue, important } + }; + } + return null; } /** @@ -887,8 +865,17 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const borderWidth = this.getBorderWidth(value, important); - return borderWidth ? { 'border-bottom-width': borderWidth['border-bottom-width'] } : null; + const lowerValue = value.toLowerCase(); + const parsedValue = + BORDER_WIDTH.includes(lowerValue) || CSSStyleDeclarationValueParser.getGlobal(lowerValue) + ? lowerValue + : CSSStyleDeclarationValueParser.getLength(value); + if (parsedValue) { + return { + 'border-bottom-width': { value: parsedValue, important } + }; + } + return null; } /** @@ -904,8 +891,17 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const borderWidth = this.getBorderWidth(value, important); - return borderWidth ? { 'border-left-width': borderWidth['border-left-width'] } : null; + const lowerValue = value.toLowerCase(); + const parsedValue = + BORDER_WIDTH.includes(lowerValue) || CSSStyleDeclarationValueParser.getGlobal(lowerValue) + ? lowerValue + : CSSStyleDeclarationValueParser.getLength(value); + if (parsedValue) { + return { + 'border-left-width': { value: parsedValue, important } + }; + } + return null; } /** @@ -921,8 +917,13 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const borderStyle = this.getBorderStyle(value, important); - return borderStyle ? { 'border-top-style': borderStyle['border-top-style'] } : null; + const lowerValue = value.toLowerCase(); + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || BORDER_STYLE.includes(lowerValue)) { + return { + 'border-top-style': { value: lowerValue, important } + }; + } + return null; } /** @@ -938,8 +939,13 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const borderStyle = this.getBorderStyle(value, important); - return borderStyle ? { 'border-right-style': borderStyle['border-right-style'] } : null; + const lowerValue = value.toLowerCase(); + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || BORDER_STYLE.includes(lowerValue)) { + return { + 'border-right-style': { value: lowerValue, important } + }; + } + return null; } /** @@ -955,8 +961,13 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const borderStyle = this.getBorderStyle(value, important); - return borderStyle ? { 'border-bottom-style': borderStyle['border-bottom-style'] } : null; + const lowerValue = value.toLowerCase(); + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || BORDER_STYLE.includes(lowerValue)) { + return { + 'border-bottom-style': { value: lowerValue, important } + }; + } + return null; } /** @@ -972,8 +983,13 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const borderStyle = this.getBorderStyle(value, important); - return borderStyle ? { 'border-left-style': borderStyle['border-left-style'] } : null; + const lowerValue = value.toLowerCase(); + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || BORDER_STYLE.includes(lowerValue)) { + return { + 'border-left-style': { value: lowerValue, important } + }; + } + return null; } /** @@ -989,8 +1005,14 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const borderColor = this.getBorderColor(value, important); - return borderColor ? { 'border-top-color': borderColor['border-top-color'] } : null; + const color = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getColor(value); + return color + ? { + 'border-top-color': { value: color, important } + } + : null; } /** @@ -1006,8 +1028,14 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const borderColor = this.getBorderColor(value, important); - return borderColor ? { 'border-right-color': borderColor['border-right-color'] } : null; + const color = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getColor(value); + return color + ? { + 'border-right-color': { value: color, important } + } + : null; } /** @@ -1023,8 +1051,14 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const borderColor = this.getBorderColor(value, important); - return borderColor ? { 'border-bottom-color': borderColor['border-bottom-color'] } : null; + const color = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getColor(value); + return color + ? { + 'border-bottom-color': { value: color, important } + } + : null; } /** @@ -1040,8 +1074,14 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const borderColor = this.getBorderColor(value, important); - return borderColor ? { 'border-left-color': borderColor['border-left-color'] } : null; + const color = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getColor(value); + return color + ? { + 'border-left-color': { value: color, important } + } + : null; } /** @@ -1056,23 +1096,21 @@ export default class CSSStyleDeclarationPropertySetParser { important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const parts = value.split(/ +/); - const topLeft = parts[0] ? CSSStyleDeclarationValueParser.getMeasurement(parts[0]) : ''; - const topRight = parts[1] ? CSSStyleDeclarationValueParser.getMeasurement(parts[1]) : ''; - const bottomRight = parts[2] ? CSSStyleDeclarationValueParser.getMeasurement(parts[2]) : ''; - const bottomLeft = parts[3] ? CSSStyleDeclarationValueParser.getMeasurement(parts[3]) : ''; - const properties = {}; + const topLeft = parts[0] ? this.getBorderTopLeftRadius(parts[0], important) : ''; + const topRight = parts[1] ? this.getBorderTopRightRadius(parts[1], important) : ''; + const bottomRight = parts[2] ? this.getBorderTopRightRadius(parts[2], important) : ''; + const bottomLeft = parts[3] ? this.getBorderTopRightRadius(parts[3], important) : ''; - properties['border-top-left-radius'] = { important, value: topLeft }; - properties['border-top-right-radius'] = { important, value: topRight || topLeft }; - properties['border-bottom-right-radius'] = { important, value: bottomRight || topLeft }; - properties['border-bottom-left-radius'] = { - important, - value: bottomLeft || topRight || topLeft - }; + if (topLeft && topRight !== null && bottomRight !== null && bottomLeft !== null) { + return { + ...topLeft, + ...topRight, + ...bottomRight, + ...bottomLeft + }; + } - return topLeft && topRight !== null && bottomRight !== null && bottomLeft !== null - ? properties - : null; + return null; } /** @@ -1086,8 +1124,8 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const radius = this.getBorderRadius(value, important); - return radius ? { 'border-top-left-radius': radius['border-top-left-radius'] } : null; + const radius = CSSStyleDeclarationValueParser.getMeasurement(value); + return radius ? { 'border-top-left-radius': { important, value: radius } } : null; } /** @@ -1101,8 +1139,8 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const radius = this.getBorderRadius(value, important); - return radius ? { 'border-top-right-radius': radius['border-top-right-radius'] } : null; + const radius = CSSStyleDeclarationValueParser.getMeasurement(value); + return radius ? { 'border-top-right-radius': { important, value: radius } } : null; } /** @@ -1116,8 +1154,8 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const radius = this.getBorderRadius(value, important); - return radius ? { 'border-bottom-right-radius': radius['border-bottom-right-radius'] } : null; + const radius = CSSStyleDeclarationValueParser.getMeasurement(value); + return radius ? { 'border-bottom-right-radius': { important, value: radius } } : null; } /** @@ -1131,8 +1169,8 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const radius = this.getBorderRadius(value, important); - return radius ? { 'border-bottom-left-radius': radius['border-bottom-left-radius'] } : null; + const radius = CSSStyleDeclarationValueParser.getMeasurement(value); + return radius ? { 'border-bottom-left-radius': { important, value: radius } } : null; } /** @@ -1146,14 +1184,25 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const border = this.getBorder(value, important); - return border - ? { - 'border-top-width': border['border-top-width'], - 'border-top-style': border['border-top-style'], - 'border-top-color': border['border-top-color'] - } - : null; + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); + const parts = value.split(/ +/); + const borderWidth = + globalValue || parts[0] ? this.getBorderTopWidth(globalValue || parts[0], important) : ''; + const borderStyle = + globalValue || parts[1] ? this.getBorderTopStyle(globalValue || parts[1], important) : ''; + const borderColor = + globalValue || parts[2] ? this.getBorderTopColor(globalValue || parts[2], important) : ''; + + if (borderWidth && borderStyle !== null && borderColor !== null) { + return { + ...this.getBorderImage('initial', important), + ...borderWidth, + ...borderStyle, + ...borderColor + }; + } + + return null; } /** @@ -1167,14 +1216,25 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const border = this.getBorder(value, important); - return border - ? { - 'border-right-width': border['border-right-width'], - 'border-right-style': border['border-right-style'], - 'border-right-color': border['border-right-color'] - } - : null; + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); + const parts = value.split(/ +/); + const borderWidth = + globalValue || parts[0] ? this.getBorderRightWidth(globalValue || parts[0], important) : ''; + const borderStyle = + globalValue || parts[1] ? this.getBorderRightStyle(globalValue || parts[1], important) : ''; + const borderColor = + globalValue || parts[2] ? this.getBorderRightColor(globalValue || parts[2], important) : ''; + + if (borderWidth && borderStyle !== null && borderColor !== null) { + return { + ...this.getBorderImage('initial', important), + ...borderWidth, + ...borderStyle, + ...borderColor + }; + } + + return null; } /** @@ -1188,14 +1248,25 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const border = this.getBorder(value, important); - return border - ? { - 'border-bottom-width': border['border-bottom-width'], - 'border-bottom-style': border['border-bottom-style'], - 'border-bottom-color': border['border-bottom-color'] - } - : null; + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); + const parts = value.split(/ +/); + const borderWidth = + globalValue || parts[0] ? this.getBorderBottomWidth(globalValue || parts[0], important) : ''; + const borderStyle = + globalValue || parts[1] ? this.getBorderBottomStyle(globalValue || parts[1], important) : ''; + const borderColor = + globalValue || parts[2] ? this.getBorderBottomColor(globalValue || parts[2], important) : ''; + + if (borderWidth && borderStyle !== null && borderColor !== null) { + return { + ...this.getBorderImage('initial', important), + ...borderWidth, + ...borderStyle, + ...borderColor + }; + } + + return null; } /** @@ -1209,14 +1280,25 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const border = this.getBorder(value, important); - return border - ? { - 'border-left-width': border['border-left-width'], - 'border-left-style': border['border-left-style'], - 'border-left-color': border['border-left-color'] - } - : null; + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); + const parts = value.split(/ +/); + const borderWidth = + globalValue || parts[0] ? this.getBorderLeftWidth(globalValue || parts[0], important) : ''; + const borderStyle = + globalValue || parts[1] ? this.getBorderLeftStyle(globalValue || parts[1], important) : ''; + const borderColor = + globalValue || parts[2] ? this.getBorderLeftColor(globalValue || parts[2], important) : ''; + + if (borderWidth && borderStyle !== null && borderColor !== null) { + return { + ...this.getBorderImage('initial', important), + ...borderWidth, + ...borderStyle, + ...borderColor + }; + } + + return null; } /** @@ -1230,18 +1312,21 @@ export default class CSSStyleDeclarationPropertySetParser { important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const parts = value.split(/ +/); - const top = parts[0] ? CSSStyleDeclarationValueParser.getMeasurement(parts[0]) : ''; - const right = parts[1] ? CSSStyleDeclarationValueParser.getMeasurement(parts[1]) : ''; - const bottom = parts[2] ? CSSStyleDeclarationValueParser.getMeasurement(parts[2]) : ''; - const left = parts[3] ? CSSStyleDeclarationValueParser.getMeasurement(parts[3]) : ''; - const properties = {}; + const top = parts[0] ? this.getPaddingTop(parts[0], important) : ''; + const right = parts[1] ? this.getPaddingRight(parts[1], important) : ''; + const bottom = parts[2] ? this.getPaddingBottom(parts[2], important) : ''; + const left = parts[3] ? this.getPaddingLeft(parts[3], important) : ''; - properties['padding-top'] = { important, value: top }; - properties['padding-right'] = { important, value: right || top }; - properties['padding-bottom'] = { important, value: bottom || top }; - properties['padding-left'] = { important, value: left || right || top }; + if (top && right !== null && bottom !== null && left !== null) { + return { + ...top, + ...right, + ...bottom, + ...left + }; + } - return top && right !== null && bottom !== null && left !== null ? properties : null; + return null; } /** @@ -1255,7 +1340,9 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const padding = CSSStyleDeclarationValueParser.getMeasurement(value); + const padding = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getMeasurement(value); return padding ? { 'padding-top': { value: padding, important } } : null; } @@ -1270,7 +1357,9 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const padding = CSSStyleDeclarationValueParser.getMeasurement(value); + const padding = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getMeasurement(value); return padding ? { 'padding-right': { value: padding, important } } : null; } @@ -1285,7 +1374,9 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const padding = CSSStyleDeclarationValueParser.getMeasurement(value); + const padding = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getMeasurement(value); return padding ? { 'padding-bottom': { value: padding, important } } : null; } @@ -1300,7 +1391,9 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const padding = CSSStyleDeclarationValueParser.getMeasurement(value); + const padding = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getMeasurement(value); return padding ? { 'padding-left': { value: padding, important } } : null; } @@ -1316,18 +1409,21 @@ export default class CSSStyleDeclarationPropertySetParser { important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const parts = value.split(/ +/); - const top = parts[0] ? CSSStyleDeclarationValueParser.getMeasurementOrAuto(parts[0]) : ''; - const right = parts[1] ? CSSStyleDeclarationValueParser.getMeasurementOrAuto(parts[1]) : ''; - const bottom = parts[2] ? CSSStyleDeclarationValueParser.getMeasurementOrAuto(parts[2]) : ''; - const left = parts[3] ? CSSStyleDeclarationValueParser.getMeasurementOrAuto(parts[3]) : ''; - const properties = {}; + const top = parts[0] ? this.getMarginTop(parts[0], important) : ''; + const right = parts[1] ? this.getMarginRight(parts[1], important) : ''; + const bottom = parts[2] ? this.getMarginBottom(parts[2], important) : ''; + const left = parts[3] ? this.getMarginLeft(parts[3], important) : ''; - properties['margin-top'] = { important, value: top }; - properties['margin-right'] = { important, value: right || top }; - properties['margin-bottom'] = { important, value: bottom || top }; - properties['margin-left'] = { important, value: left || right || top }; + if (top && right !== null && bottom !== null && left !== null) { + return { + ...top, + ...right, + ...bottom, + ...left + }; + } - return top && right !== null && bottom !== null && left !== null ? properties : null; + return null; } /** @@ -1341,7 +1437,9 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const margin = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + const margin = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); return margin ? { 'margin-top': { value: margin, important } } : null; } @@ -1356,7 +1454,9 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const margin = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + const margin = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); return margin ? { 'margin-right': { value: margin, important } } : null; } @@ -1371,7 +1471,9 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const margin = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + const margin = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); return margin ? { 'margin-bottom': { value: margin, important } } : null; } @@ -1386,7 +1488,9 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const margin = CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + const margin = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); return margin ? { 'margin-left': { value: margin, important } } : null; } @@ -1402,6 +1506,15 @@ export default class CSSStyleDeclarationPropertySetParser { important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const lowerValue = value.trim().toLowerCase(); + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); + + if (globalValue) { + return { + 'flex-grow': { value: globalValue, important }, + 'flex-shrink': { value: globalValue, important }, + 'flex-basis': { value: globalValue, important } + }; + } switch (lowerValue) { case 'none': @@ -1416,18 +1529,6 @@ export default class CSSStyleDeclarationPropertySetParser { 'flex-shrink': { important, value: '1' }, 'flex-basis': { important, value: 'auto' } }; - case 'initial': - return { - 'flex-grow': { important, value: '0' }, - 'flex-shrink': { important, value: '0' }, - 'flex-basis': { important, value: 'auto' } - }; - case 'inherit': - return { - 'flex-grow': { important, value: 'inherit' }, - 'flex-shrink': { important, value: 'inherit' }, - 'flex-basis': { important, value: 'inherit' } - }; } const parts = value.split(/ +/); From 53605a1eb71901637c3a29fa8056a928e2d6964f Mon Sep 17 00:00:00 2001 From: Tanimodori Date: Sat, 3 Sep 2022 21:57:31 +0800 Subject: [PATCH 32/84] #576@patch: Fix querySelector issue on tag and id. --- .../src/query-selector/SelectorItem.ts | 23 +++++++++++++------ .../test/query-selector/QuerySelector.test.ts | 10 ++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/packages/happy-dom/src/query-selector/SelectorItem.ts b/packages/happy-dom/src/query-selector/SelectorItem.ts index a5aa0ead4..dc0b2389b 100644 --- a/packages/happy-dom/src/query-selector/SelectorItem.ts +++ b/packages/happy-dom/src/query-selector/SelectorItem.ts @@ -7,6 +7,7 @@ const ATTRIBUTE_NAME_REGEXP = /[^a-zA-Z0-9-_$]/; const PSUEDO_REGEXP = /:([a-zA-Z-]+)\(([0-9n+-]+|odd|even)\)|:not\(([^)]+)\)|:([a-zA-Z-]+)/g; const CLASS_REGEXP = /\.([a-zA-Z0-9-_$]+)/g; const TAG_NAME_REGEXP = /^[a-zA-Z0-9-]+/; +const ID_REGEXP = /#[A-Za-z][-A-Za-z0-9_]*/g; /** * Selector item. @@ -31,16 +32,22 @@ export default class SelectorItem { const baseSelector = selector.replace(new RegExp(PSUEDO_REGEXP, 'g'), ''); this.isAll = baseSelector === '*'; - this.isID = !this.isAll ? selector.startsWith('#') : false; - this.isAttribute = !this.isAll && !this.isID && baseSelector.includes('['); + this.isID = !this.isAll ? selector.indexOf('#') !== -1 : false; + this.isAttribute = !this.isAll && baseSelector.includes('['); // If baseSelector !== selector then some psuedo selector was replaced above - this.isPseudo = !this.isAll && !this.isID && baseSelector !== selector; - this.isClass = !this.isAll && !this.isID && new RegExp(CLASS_REGEXP, 'g').test(baseSelector); - this.tagName = !this.isAll && !this.isID ? baseSelector.match(TAG_NAME_REGEXP) : null; + this.isPseudo = !this.isAll && baseSelector !== selector; + this.isClass = !this.isAll && new RegExp(CLASS_REGEXP, 'g').test(baseSelector); + this.tagName = !this.isAll ? baseSelector.match(TAG_NAME_REGEXP) : null; this.tagName = this.tagName ? this.tagName[0].toUpperCase() : null; this.isTagName = this.tagName !== null; this.selector = selector; - this.id = !this.isAll && this.isID ? baseSelector.replace('#', '') : null; + this.id = null; + if (!this.isAll && this.isID) { + const idMatches = baseSelector.match(ID_REGEXP); + if (idMatches) { + this.id = idMatches[0].replace('#', ''); + } + } } /** @@ -59,7 +66,9 @@ export default class SelectorItem { // ID Match if (this.isID) { - return this.id === element.id; + if (this.id !== element.id) { + return false; + } } // Tag name match diff --git a/packages/happy-dom/test/query-selector/QuerySelector.test.ts b/packages/happy-dom/test/query-selector/QuerySelector.test.ts index f95192bff..305ea6925 100644 --- a/packages/happy-dom/test/query-selector/QuerySelector.test.ts +++ b/packages/happy-dom/test/query-selector/QuerySelector.test.ts @@ -560,6 +560,16 @@ describe('QuerySelector', () => { expect(div1.querySelector('span.spanClass')).toBe(span); }); + it('Returns div with a specific id and tag name matching "div#divId".', () => { + const div1 = document.createElement('div'); + const div2 = document.createElement('div'); + const div3 = document.createElement('div'); + div3.id = 'divId'; + div1.appendChild(div2); + div2.appendChild(div3); + expect(div1.querySelector('div#divId')).toBe(div3); + }); + it('Returns span with a specific class name and tag name matching "custom-element.class1".', () => { const div = document.createElement('div'); const customElement1 = document.createElement('custom-element'); From 27906995d77d4415d984160283f3d3c6992bc56d Mon Sep 17 00:00:00 2001 From: David Ortner Date: Mon, 5 Sep 2022 17:33:53 +0200 Subject: [PATCH 33/84] #344@trivial: Continues on CSSStyleDeclaration. --- .../CSSStyleDeclarationPropertyGetParser.ts | 148 ++- .../CSSStyleDeclarationPropertyManager.ts | 20 +- .../CSSStyleDeclarationPropertySetParser.ts | 1014 +++++++++++------ .../CSSStyleDeclarationValueParser.ts | 13 + 4 files changed, 789 insertions(+), 406 deletions(-) diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts index 3dfc48f21..81fa178b1 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts @@ -1,3 +1,4 @@ +import CSSStyleDeclarationValueParser from './CSSStyleDeclarationValueParser'; import ICSSStyleDeclarationPropertyValue from './ICSSStyleDeclarationPropertyValue'; /** @@ -13,12 +14,24 @@ export default class CSSStyleDeclarationPropertyGetParser { public static getMargin(properties: { [k: string]: ICSSStyleDeclarationPropertyValue; }): ICSSStyleDeclarationPropertyValue { - return this.getPositionedValue( - properties['margin-top'], - properties['margin-right'], - properties['margin-bottom'], - properties['margin-left'] - ); + if ( + CSSStyleDeclarationValueParser.getGlobal(properties['margin-top']?.value) || + CSSStyleDeclarationValueParser.getGlobal(properties['margin-right']?.value) || + CSSStyleDeclarationValueParser.getGlobal(properties['margin-bottom']?.value) || + CSSStyleDeclarationValueParser.getGlobal(properties['margin-left']?.value) + ) { + return null; + } + + return { + important: ![ + properties['margin-top'].important, + properties['margin-right'].important, + properties['margin-bottom'].important, + properties['margin-left'].important + ].some((important) => important === false), + value: `${properties['margin-top'].value} ${properties['margin-right'].value} ${properties['margin-bottom'].value} ${properties['margin-left'].value}` + }; } /** @@ -30,12 +43,24 @@ export default class CSSStyleDeclarationPropertyGetParser { public static getPadding(properties: { [k: string]: ICSSStyleDeclarationPropertyValue; }): ICSSStyleDeclarationPropertyValue { - return this.getPositionedValue( - properties['padding-top'], - properties['padding-right'], - properties['padding-bottom'], - properties['padding-left'] - ); + if ( + CSSStyleDeclarationValueParser.getGlobal(properties['padding-top']?.value) || + CSSStyleDeclarationValueParser.getGlobal(properties['padding-right']?.value) || + CSSStyleDeclarationValueParser.getGlobal(properties['padding-bottom']?.value) || + CSSStyleDeclarationValueParser.getGlobal(properties['padding-left']?.value) + ) { + return null; + } + + return { + important: ![ + properties['padding-top'].important, + properties['padding-right'].important, + properties['padding-bottom'].important, + properties['padding-left'].important + ].some((important) => important === false), + value: `${properties['padding-top'].value} ${properties['padding-right'].value} ${properties['padding-bottom'].value} ${properties['padding-left'].value}` + }; } /** @@ -52,33 +77,33 @@ export default class CSSStyleDeclarationPropertyGetParser { 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-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 || + !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 || !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 + !properties['border-image-repeat']?.value || + !CSSStyleDeclarationValueParser.getNonGlobalOrInitial(properties['border-top-width'].value) || + !CSSStyleDeclarationValueParser.getNonGlobalOrInitial(properties['border-top-style'].value) || + !CSSStyleDeclarationValueParser.getNonGlobalOrInitial(properties['border-top-color'].value) ) { return null; } const values = [properties['border-top-width'].value]; - if ( - properties['border-top-style']?.value && - properties['border-top-style'].value === properties['border-right-style'].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 - ) { + if (properties['border-top-style'].value !== 'initial') { values.push(properties['border-top-style'].value); } - 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 - ) { + if (properties['border-top-color'].value !== 'initial') { values.push(properties['border-top-color'].value); } @@ -310,12 +335,24 @@ export default class CSSStyleDeclarationPropertyGetParser { public static getBorderRadius(properties: { [k: string]: ICSSStyleDeclarationPropertyValue; }): ICSSStyleDeclarationPropertyValue { - return this.getPositionedValue( - properties['border-top-left-radius'], - properties['border-top-right-radius'], - properties['border-bottom-right-radius'], - properties['border-bottom-left-radius'] - ); + if ( + CSSStyleDeclarationValueParser.getGlobal(properties['border-top-left-radius']?.value) || + CSSStyleDeclarationValueParser.getGlobal(properties['border-top-right-radius']?.value) || + CSSStyleDeclarationValueParser.getGlobal(properties['border-bottom-right-radius']?.value) || + CSSStyleDeclarationValueParser.getGlobal(properties['border-bottom-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['margin-left'].important + ].some((important) => important === false), + value: `${properties['border-top-left-radius'].value} ${properties['border-top-right-radius'].value} ${properties['border-bottom-right-radius'].value} ${properties['border-bottom-left-radius'].value}` + }; } /** @@ -435,51 +472,4 @@ export default class CSSStyleDeclarationPropertyGetParser { value: values.join(' ') }; } - - /** - * Returns a positioned value. - * - * @param top Top. - * @param right Right - * @param bottom Bottom. - * @param left Left. - * @returns Property value - */ - public static getPositionedValue( - top?: ICSSStyleDeclarationPropertyValue, - right?: ICSSStyleDeclarationPropertyValue, - bottom?: ICSSStyleDeclarationPropertyValue, - left?: ICSSStyleDeclarationPropertyValue - ): ICSSStyleDeclarationPropertyValue { - if (!top?.value) { - return null; - } - - const values = [top.value]; - - if (right?.value && right.value !== top.value) { - values.push(right.value); - } - - if (bottom?.value && bottom.value !== top.value) { - for (let i = values.length - 1; i < 1; i++) { - values.push('0px'); - } - values.push(bottom.value); - } - - if (left?.value && left.value !== right?.value) { - for (let i = values.length - 1; i < 2; i++) { - values.push('0px'); - } - values.push(left.value); - } - - return { - important: ![top?.important, right?.important, bottom?.important, left?.important].some( - (important) => important === false - ), - value: values.join(' ') - }; - } } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts index 12c9fe919..25dc18513 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts @@ -148,6 +148,13 @@ export default class CSSStyleDeclarationPropertyManager { delete this.properties['border-bottom-color']; delete this.properties['border-left-color']; break; + case 'border-image': + delete this.properties['border-image-source']; + delete this.properties['border-image-slice']; + delete this.properties['border-image-width']; + delete this.properties['border-image-outset']; + delete this.properties['border-image-repeat']; + break; case 'border-radius': delete this.properties['border-top-left-radius']; delete this.properties['border-top-right-radius']; @@ -427,12 +434,13 @@ export default class CSSStyleDeclarationPropertyManager { properties = CSSStyleDeclarationPropertySetParser.getFloodColor(value, important); break; default: - const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); - properties = value - ? { - [name]: { value: globalValue ? globalValue : value, important } - } - : null; + const trimmedValue = value.trim(); + if (trimmedValue) { + const globalValue = CSSStyleDeclarationValueParser.getGlobal(trimmedValue); + properties = { + [name]: { value: globalValue || trimmedValue, important } + }; + } break; } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts index c15d35cf0..e1d7092b2 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts @@ -17,8 +17,9 @@ const BORDER_STYLE = [ const BORDER_WIDTH = ['thin', 'medium', 'thick']; const BORDER_COLLAPSE = ['separate', 'collapse']; const BACKGROUND_REPEAT = ['repeat', 'repeat-x', 'repeat-y', 'no-repeat']; +const BACKGROUND_ORIGIN = ['border-box', 'padding-box', 'content-box']; +const BACKGROUND_CLIP = ['border-box', 'padding-box', 'content-box']; 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']; const CLEAR = ['none', 'left', 'right', 'both']; const FLOAT = ['none', 'left', 'right']; @@ -108,113 +109,6 @@ export default class CSSStyleDeclarationPropertySetParser { return null; } - /** - * Returns background repeat. - * - * @param value Value. - * @param important Important. - * @returns Property values - */ - public static getBackgroundRepeat( - value: string, - important: boolean - ): { - [key: string]: ICSSStyleDeclarationPropertyValue; - } { - const lowerValue = value.toLowerCase(); - if ( - CSSStyleDeclarationValueParser.getGlobal(lowerValue) || - BACKGROUND_REPEAT.includes(lowerValue) - ) { - return { 'background-repeat': { value: lowerValue, important } }; - } - return null; - } - - /** - * Returns background attachment. - * - * @param value Value. - * @param important Important. - * @returns Property values - */ - public static getBackgroundAttachment( - value: string, - important: boolean - ): { - [key: string]: ICSSStyleDeclarationPropertyValue; - } { - const lowerValue = value.toLowerCase(); - if ( - CSSStyleDeclarationValueParser.getGlobal(lowerValue) || - BACKGROUND_ATTACHMENT.includes(lowerValue) - ) { - return { 'background-attachment': { value: lowerValue, important } }; - } - return null; - } - - /** - * Returns URL. - * - * Based on: - * https://github.com/jsdom/cssstyle/blob/master/lib/properties/backgroundPosition.js - * - * @param value Value. - * @param important Important. - * @returns Property values - */ - public static getBackgroundPosition( - value: string, - important: boolean - ): { - [key: string]: ICSSStyleDeclarationPropertyValue; - } { - 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; - } - if (parts.length === 1) { - if (CSSStyleDeclarationValueParser.getLength(parts[0])) { - return { 'background-position': { value, important } }; - } - if (parts[0]) { - const lowerValue = value.toLowerCase(); - if (BACKGROUND_POSITION.includes(lowerValue) || lowerValue === 'inherit') { - return { 'background-position': { value: lowerValue, important } }; - } - } - return null; - } - if ( - (CSSStyleDeclarationValueParser.getLength(parts[0]) || - CSSStyleDeclarationValueParser.getPercentage(parts[0])) && - (CSSStyleDeclarationValueParser.getLength(parts[1]) || - CSSStyleDeclarationValueParser.getPercentage(parts[1])) - ) { - return { 'background-position': { value: value.toLowerCase(), important } }; - } - if ( - BACKGROUND_POSITION.includes(parts[0].toLowerCase()) && - BACKGROUND_POSITION.includes(parts[1].toLowerCase()) - ) { - return { - 'background-position': { - value: `${parts[0].toLowerCase()} ${parts[1].toLowerCase()}`, - important - } - }; - } - return null; - } - /** * Returns display. * @@ -441,23 +335,38 @@ export default class CSSStyleDeclarationPropertySetParser { important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); - const parts = value.split(/ +/); - 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) : ''; - if (borderWidth && borderStyle !== null && borderColor !== null) { + if (globalValue) { return { - ...this.getBorderImage('initial', important), - ...borderWidth, - ...borderStyle, - ...borderColor + ...this.getBorderWidth(globalValue, important), + ...this.getBorderStyle(globalValue, important), + ...this.getBorderColor(globalValue, important), + ...this.getBorderImage(globalValue, important) }; } - return null; + + const properties = { + ...this.getBorderWidth('initial', important), + ...this.getBorderStyle('initial', important), + ...this.getBorderColor('initial', important), + ...this.getBorderImage('none', important) + }; + + const parts = value.split(/ +/); + + for (const part of parts) { + const width = this.getBorderWidth(part, important); + const style = this.getBorderStyle(part, important); + const color = this.getBorderColor(part, important); + + if (width === null && style === null && color === null) { + return null; + } + + Object.assign(properties, width, style, color); + } + + return properties; } /** @@ -473,13 +382,33 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const properties = { - ...this.getBorderTopWidth(value, important), - ...this.getBorderRightWidth(value, important), - ...this.getBorderBottomWidth(value, important), - ...this.getBorderLeftWidth(value, important) + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); + + if (globalValue) { + return { + ...this.getBorderTopWidth(globalValue, important), + ...this.getBorderRightWidth(globalValue, important), + ...this.getBorderBottomWidth(globalValue, important), + ...this.getBorderLeftWidth(globalValue, important) + }; + } + + const parts = value.split(/ +/); + const top = this.getBorderTopWidth(parts[0], important); + const right = this.getBorderRightWidth(parts[1] || parts[0], important); + const bottom = this.getBorderBottomWidth(parts[2] || parts[0], important); + const left = this.getBorderLeftWidth(parts[3] || parts[1] || parts[0], important); + + if (!top || !right || !bottom || !left) { + return null; + } + + return { + ...top, + ...right, + ...bottom, + ...left }; - return Object.keys(properties).length ? properties : null; } /** * Returns border style. @@ -494,13 +423,33 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const properties = { - ...this.getBorderTopStyle(value, important), - ...this.getBorderRightStyle(value, important), - ...this.getBorderBottomStyle(value, important), - ...this.getBorderLeftStyle(value, important) + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); + + if (globalValue) { + return { + ...this.getBorderTopStyle(globalValue, important), + ...this.getBorderRightStyle(globalValue, important), + ...this.getBorderBottomStyle(globalValue, important), + ...this.getBorderLeftStyle(globalValue, important) + }; + } + + const parts = value.split(/ +/); + const top = this.getBorderTopStyle(parts[0], important); + const right = this.getBorderRightStyle(parts[1] || parts[0], important); + const bottom = this.getBorderBottomStyle(parts[2] || parts[0], important); + const left = this.getBorderLeftStyle(parts[3] || parts[1] || parts[0], important); + + if (!top || !right || !bottom || !left) { + return null; + } + + return { + ...top, + ...right, + ...bottom, + ...left }; - return Object.keys(properties).length ? properties : null; } /** @@ -516,13 +465,33 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const properties = { - ...this.getBorderTopStyle(value, important), - ...this.getBorderRightStyle(value, important), - ...this.getBorderBottomStyle(value, important), - ...this.getBorderLeftStyle(value, important) + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); + + if (globalValue) { + return { + ...this.getBorderTopColor(globalValue, important), + ...this.getBorderRightColor(globalValue, important), + ...this.getBorderBottomColor(globalValue, important), + ...this.getBorderLeftColor(globalValue, important) + }; + } + + const parts = value.split(/ +/); + const top = this.getBorderTopColor(parts[0], important); + const right = this.getBorderRightColor(parts[1] || parts[0], important); + const bottom = this.getBorderBottomColor(parts[2] || parts[0], important); + const left = this.getBorderLeftColor(parts[3] || parts[1] || parts[0], important); + + if (!top || !right || !bottom || !left) { + return null; + } + + return { + ...top, + ...right, + ...bottom, + ...left }; - return Object.keys(properties).length ? properties : null; } /** @@ -542,57 +511,52 @@ export default class CSSStyleDeclarationPropertySetParser { 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 } + ...this.getBorderImageSource(globalValue, important), + ...this.getBorderImageSlice(globalValue, important), + ...this.getBorderImageWidth(globalValue, important), + ...this.getBorderImageOutset(globalValue, important), + ...this.getBorderImageRepeat(globalValue, important) }; } - const parts = value.toLowerCase().split(/ +/); - const borderImageSource = parts[0] ? this.getBorderImageSource(parts[0], important) : ''; - const repeatValue = []; + const parsedValue = value.replace(/[ ]*\/[ ]*/g, '/'); + const parts = parsedValue.split(/ +/); + const properties = { + ...this.getBorderImageSource('none', important), + ...this.getBorderImageSlice('100%', important), + ...this.getBorderImageWidth('1', important), + ...this.getBorderImageOutset('0', important), + ...this.getBorderImageRepeat('stretch', important) + }; - parts.splice(0, 1); + for (const part of parts) { + if (part.includes('/')) { + const [slice, width, outset] = part.split('/'); + const borderImageSlice = this.getBorderImageSlice(slice, important); + const borderImageWidth = this.getBorderImageWidth(width, important); + const borderImageOutset = this.getBorderImageOutset(outset, important); + + if (borderImageSlice === null || borderImageWidth === null || borderImageOutset === null) { + return null; + } - 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); - } - } + Object.assign(properties, borderImageSlice); + Object.assign(properties, borderImageWidth); + Object.assign(properties, borderImageOutset); + } else { + const source = this.getBorderImageSource(part, important); + const repeat = this.getBorderImageRepeat(part, important); - 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) - : ''; + if (source === null && repeat === null) { + return null; + } - if ( - borderImageSource && - borderImageRepeat !== null && - borderImageSlice !== null && - borderImageWidth !== null && - borderImageOutset !== null - ) { - return { - ...borderImageSource, - ...borderImageSlice, - ...borderImageWidth, - ...borderImageOutset, - ...borderImageRepeat - }; + Object.assign(properties, source); + Object.assign(properties, repeat); + } } - return null; + return properties; } /** @@ -1311,22 +1275,33 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const parts = value.split(/ +/); - const top = parts[0] ? this.getPaddingTop(parts[0], important) : ''; - const right = parts[1] ? this.getPaddingRight(parts[1], important) : ''; - const bottom = parts[2] ? this.getPaddingBottom(parts[2], important) : ''; - const left = parts[3] ? this.getPaddingLeft(parts[3], important) : ''; + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); - if (top && right !== null && bottom !== null && left !== null) { + if (globalValue) { return { - ...top, - ...right, - ...bottom, - ...left + ...this.getPaddingTop(globalValue, important), + ...this.getPaddingRight(globalValue, important), + ...this.getPaddingBottom(globalValue, important), + ...this.getPaddingLeft(globalValue, important) }; } - return null; + const parts = value.split(/ +/); + const top = this.getPaddingTop(parts[0], important); + const right = this.getPaddingRight(parts[1] || '0', important); + const bottom = this.getPaddingBottom(parts[2] || '0', important); + const left = this.getPaddingLeft(parts[3] || '0', important); + + if (!top || !right || !bottom || !left) { + return null; + } + + return { + ...top, + ...right, + ...bottom, + ...left + }; } /** @@ -1408,22 +1383,33 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const parts = value.split(/ +/); - const top = parts[0] ? this.getMarginTop(parts[0], important) : ''; - const right = parts[1] ? this.getMarginRight(parts[1], important) : ''; - const bottom = parts[2] ? this.getMarginBottom(parts[2], important) : ''; - const left = parts[3] ? this.getMarginLeft(parts[3], important) : ''; + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); - if (top && right !== null && bottom !== null && left !== null) { + if (globalValue) { return { - ...top, - ...right, - ...bottom, - ...left + ...this.getMarginTop(globalValue, important), + ...this.getMarginRight(globalValue, important), + ...this.getMarginBottom(globalValue, important), + ...this.getMarginLeft(globalValue, important) }; } - return null; + const parts = value.split(/ +/); + const top = this.getMarginTop(parts[0], important); + const right = this.getMarginRight(parts[1] || '0', important); + const bottom = this.getMarginBottom(parts[2] || '0', important); + const left = this.getMarginLeft(parts[3] || '0', important); + + if (!top || !right || !bottom || !left) { + return null; + } + + return { + ...top, + ...right, + ...bottom, + ...left + }; } /** @@ -1510,31 +1496,31 @@ export default class CSSStyleDeclarationPropertySetParser { if (globalValue) { return { - 'flex-grow': { value: globalValue, important }, - 'flex-shrink': { value: globalValue, important }, - 'flex-basis': { value: globalValue, important } + ...this.getFlexGrow(globalValue, important), + ...this.getFlexShrink(globalValue, important), + ...this.getFlexBasis(globalValue, important) }; } switch (lowerValue) { case 'none': return { - 'flex-grow': { important, value: '0' }, - 'flex-shrink': { important, value: '0' }, - 'flex-basis': { important, value: 'auto' } + ...this.getFlexGrow('0', important), + ...this.getFlexShrink('0', important), + ...this.getFlexBasis('auto', important) }; case 'auto': return { - 'flex-grow': { important, value: '1' }, - 'flex-shrink': { important, value: '1' }, - 'flex-basis': { important, value: 'auto' } + ...this.getFlexGrow('1', important), + ...this.getFlexShrink('1', important), + ...this.getFlexBasis('auto', important) }; } const parts = value.split(/ +/); - const flexGrow = parts[0] ? this.getFlexGrow(parts[0], important) : ''; - const flexShrink = parts[1] ? this.getFlexShrink(parts[1], important) : ''; - const flexBasis = parts[2] ? this.getFlexBasis(parts[2], important) : ''; + const flexGrow = parts[0] && this.getFlexGrow(parts[0], important); + const flexShrink = parts[1] && this.getFlexShrink(parts[1], important); + const flexBasis = parts[2] && this.getFlexBasis(parts[2], important); if (flexGrow && flexShrink && flexBasis) { return { @@ -1561,7 +1547,7 @@ export default class CSSStyleDeclarationPropertySetParser { [key: string]: ICSSStyleDeclarationPropertyValue; } { const lowerValue = value.toLowerCase(); - if (FLEX_BASIS.includes(lowerValue)) { + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || FLEX_BASIS.includes(lowerValue)) { return { 'flex-basis': { value: lowerValue, important } }; } return { 'flex-basis': { value: CSSStyleDeclarationValueParser.getLength(value), important } }; @@ -1580,7 +1566,9 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const parsedValue = CSSStyleDeclarationValueParser.getInteger(value); + const parsedValue = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getInteger(value); return parsedValue ? { 'flex-shrink': { value: parsedValue, important } } : null; } @@ -1597,7 +1585,9 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const parsedValue = CSSStyleDeclarationValueParser.getInteger(value); + const parsedValue = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getInteger(value); return parsedValue ? { 'flex-grow': { value: parsedValue, important } } : null; } @@ -1613,47 +1603,394 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const parts = value.split(/ +/); + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); - if (!parts[0]) { - return; + if (globalValue) { + return { + ...this.getBackgroundImage(globalValue, important), + ...this.getBackgroundPosition(globalValue, important), + ...this.getBackgroundSize(globalValue, important), + ...this.getBackgroundRepeat(globalValue, important), + ...this.getBackgroundAttachment(globalValue, important), + ...this.getBackgroundOrigin(globalValue, important), + ...this.getBackgroundClip(globalValue, important), + ...this.getBackgroundColor(globalValue, important) + }; } - // First value can be image if color is not specified. - if (!CSSStyleDeclarationValueParser.getColor(parts[0])) { - parts.unshift(''); + const properties = { + ...this.getBackgroundImage('none', important), + ...this.getBackgroundPosition('0% 0%', important), + ...this.getBackgroundSize('auto auto', important), + ...this.getBackgroundRepeat('repeat', important), + ...this.getBackgroundAttachment('scroll', important), + ...this.getBackgroundOrigin('padding-box', important), + ...this.getBackgroundClip('border-box', important), + ...this.getBackgroundColor('transparent', important) + }; + + const parts = value.replace(/[ ]*\/[ ]*/g, '/').split(/ +/); + + for (const part of parts) { + if (part.includes('/')) { + const [position, size] = part.split('/'); + const backgroundPosition = this.getBackgroundImage(position, important); + const backgroundSize = this.getBackgroundSize(size, important); + + if (!backgroundPosition || !backgroundSize) { + return null; + } + + Object.assign(properties, backgroundPosition, backgroundSize); + } else { + const backgroundImage = this.getBackgroundImage(part, important); + const backgroundRepeat = this.getBackgroundRepeat(part, important); + const backgroundAttachment = this.getBackgroundAttachment(part, important); + const backgroundOrigin = this.getBackgroundOrigin(part, important); + const backgroundColor = this.getBackgroundColor(part, important); + + if ( + !backgroundImage && + !backgroundRepeat && + !backgroundAttachment && + !backgroundOrigin && + !backgroundColor + ) { + return null; + } + + Object.assign( + properties, + backgroundImage, + backgroundRepeat, + backgroundAttachment, + backgroundColor + ); + } } - const color = parts[0] ? this.getBackgroundColor(parts[0], important) : ''; - const image = parts[1] ? this.getBackgroundImage(parts[1], important) : ''; - const repeat = parts[2] ? this.getBackgroundRepeat(parts[2], important) : ''; - const attachment = parts[3] ? this.getBackgroundAttachment(parts[3], important) : ''; - const position = parts[4] ? this.getBackgroundPosition(parts[4], important) : ''; - const properties = {}; + return properties; + } - if (color) { - Object.assign(properties, color); + /** + * Returns background size. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBackgroundSize( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const lowerValue = value.toLowerCase(); + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue)) { + return { 'background-size': { value: lowerValue, important } }; } - if (image) { - Object.assign(properties, image); + const imageParts = lowerValue.split(','); + const parsed = []; + + for (const imagePart of imageParts) { + const parts = imagePart.trim().split(' '); + if (parts.length !== 1 && parts.length !== 2) { + return null; + } + if (parts.length === 1) { + if ( + parts[0] !== 'cover' && + parts[0] !== 'contain' && + !CSSStyleDeclarationValueParser.getMeasurementOrAuto(parts[0]) + ) { + return null; + } + parsed.push(parts[0]); + } else { + if ( + !CSSStyleDeclarationValueParser.getMeasurementOrAuto(parts[0]) || + !CSSStyleDeclarationValueParser.getMeasurementOrAuto(parts[1]) + ) { + return null; + } + parsed.push(`${parts[0]} ${parts[1]}`); + } + } + if (parsed.length === 1) { + return { 'background-size': { value: parsed.join(', '), important } }; } + return null; + } - if (repeat) { - Object.assign(properties, repeat); + /** + * Returns background origin. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBackgroundOrigin( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const lowerValue = value.toLowerCase(); + if ( + CSSStyleDeclarationValueParser.getGlobal(lowerValue) || + BACKGROUND_ORIGIN.includes(lowerValue) + ) { + return { 'background-origin': { value: lowerValue, important } }; } + return null; + } - if (attachment) { - Object.assign(properties, attachment); + /** + * Returns background clip. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBackgroundClip( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const lowerValue = value.toLowerCase(); + if ( + CSSStyleDeclarationValueParser.getGlobal(lowerValue) || + BACKGROUND_CLIP.includes(lowerValue) + ) { + return { 'background-clip': { value: lowerValue, important } }; } + return null; + } - if (position) { - Object.assign(properties, position); + /** + * Returns background repeat. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBackgroundRepeat( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const lowerValue = value.toLowerCase(); + if ( + CSSStyleDeclarationValueParser.getGlobal(lowerValue) || + BACKGROUND_REPEAT.includes(lowerValue) + ) { + return { 'background-repeat': { value: lowerValue, important } }; } + return null; + } - return (color || image) && repeat !== null && attachment !== null && position !== null - ? properties - : null; + /** + * Returns background attachment. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBackgroundAttachment( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const lowerValue = value.toLowerCase(); + if ( + CSSStyleDeclarationValueParser.getGlobal(lowerValue) || + BACKGROUND_ATTACHMENT.includes(lowerValue) + ) { + return { 'background-attachment': { value: lowerValue, important } }; + } + return null; + } + + /** + * Returns background position. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBackgroundPosition( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); + if (globalValue) { + return { + ...this.getBackgroundPositionX(globalValue, important), + ...this.getBackgroundPositionY(globalValue, important) + }; + } + + const imageParts = value.split(','); + let x = ''; + let y = ''; + + for (const imagePart of imageParts) { + const parts = imagePart.trim().split(/ +/); + + if (x) { + x += ','; + y += ','; + } + + if (parts.length === 1) { + if (parts[0] === 'top' || parts[0] === 'bottom') { + x += 'center'; + y += parts[0]; + } else if (parts[0] === 'left' || parts[0] === 'right') { + x += parts[0]; + y += 'center'; + } else if (parts[0] === 'center') { + x += 'center'; + y += 'center'; + } + } + + if (parts.length !== 2 && parts.length !== 4) { + return null; + } + + if (parts.length === 2) { + x += parts[0] === 'top' || parts[0] === 'bottom' ? parts[1] : parts[0]; + y += parts[0] === 'left' || parts[0] === 'right' ? parts[0] : parts[1]; + } else if (parts.length === 4) { + x += + parts[0] === 'top' || parts[0] === 'bottom' || parts[1] === 'top' || parts[1] === 'bottom' + ? `${parts[2]} ${parts[3]}` + : `${parts[0]} ${parts[1]}`; + y += + parts[2] === 'left' || parts[2] === 'right' || parts[3] === 'left' || parts[3] === 'right' + ? `${parts[0]} ${parts[1]}` + : `${parts[2]} ${parts[3]}`; + } + } + + const xValue = this.getBackgroundPositionX(x, important); + const yValue = this.getBackgroundPositionY(y, important); + + if (xValue && yValue) { + return { + ...xValue, + ...yValue + }; + } + + return null; + } + + /** + * Returns background position. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBackgroundPositionX( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const lowerValue = value.toLowerCase(); + + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue)) { + return { 'background-position': { value: lowerValue, important } }; + } + + const imageParts = lowerValue.split(','); + let parsedValue = ''; + + for (const imagePart of imageParts) { + const parts = imagePart.trim().split(/ +/); + + if (parsedValue) { + parsedValue += ','; + } + + for (const part of parts) { + if ( + !CSSStyleDeclarationValueParser.getMeasurement(part) && + part !== 'left' && + part !== 'right' && + part !== 'center' + ) { + return null; + } + + if (parsedValue) { + parsedValue += ' '; + } + + parsedValue += part; + } + } + + return { 'background-position-x': { value: parsedValue, important } }; + } + + /** + * Returns background position. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getBackgroundPositionY( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const lowerValue = value.toLowerCase(); + + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue)) { + return { 'background-position': { value: lowerValue, important } }; + } + + const imageParts = lowerValue.split(','); + let parsedValue = ''; + + for (const imagePart of imageParts) { + const parts = imagePart.trim().split(/ +/); + + if (parsedValue) { + parsedValue += ','; + } + + for (const part of parts) { + if ( + !CSSStyleDeclarationValueParser.getMeasurement(part) && + part !== 'top' && + part !== 'bottom' && + part !== 'center' + ) { + return null; + } + + if (parsedValue) { + parsedValue += ' '; + } + + parsedValue += part; + } + } + + return { 'background-position-y': { value: parsedValue, important } }; } /** @@ -1667,7 +2004,9 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const color = CSSStyleDeclarationValueParser.getColor(value); + const color = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getColor(value); return color ? { @@ -1687,13 +2026,28 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const image = CSSStyleDeclarationValueParser.getURL(value); + const lowerValue = value.toLowerCase(); - return image - ? { - ['background-image']: { important, value: image } - } - : null; + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || lowerValue === 'none') { + return { 'background-image': { value: lowerValue, important } }; + } + + const parts = value.split(','); + const parsed = []; + + for (const part of parts) { + const url = CSSStyleDeclarationValueParser.getURL(part.trim()); + if (!url) { + return null; + } + parsed.push(url); + } + + if (parsed.length) { + return { 'background-image': { value: parsed.join(', '), important } }; + } + + return null; } /** @@ -1707,7 +2061,9 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const color = CSSStyleDeclarationValueParser.getColor(value); + const color = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getColor(value); return color ? { @@ -1727,7 +2083,9 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const color = CSSStyleDeclarationValueParser.getColor(value); + const color = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getColor(value); return color ? { @@ -1751,76 +2109,73 @@ export default class CSSStyleDeclarationPropertySetParser { } { const lowerValue = value.toLowerCase(); - if (SYSTEM_FONT.includes(lowerValue)) { - return { font: { value: lowerValue, important } }; - } - - const parts = value.split(/ +/); - - 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 (CSSStyleDeclarationValueParser.getGlobal(lowerValue)) { + return { + ...this.getFontStyle(lowerValue, important), + ...this.getFontVariant(lowerValue, important), + ...this.getFontWeight(lowerValue, important), + ...this.getFontStretch(lowerValue, important), + ...this.getFontSize(lowerValue, important), + ...this.getLineHeight(lowerValue, important), + ...this.getFontFamily(lowerValue, important) + }; } - if (parts.length <= 3 || !this.getFontStretch(parts[3], important)) { - parts.splice(3, 0, ''); + if (SYSTEM_FONT.includes(lowerValue)) { + return { font: { value: lowerValue, important } }; } - const [fontSizeValue, lineHeightValue] = parts[4].split('/'); + const properties = { + ...this.getFontStyle('normal', important), + ...this.getFontVariant('normal', important), + ...this.getFontWeight('normal', important), + ...this.getFontStretch('normal', important), + ...this.getLineHeight('normal', important) + }; - const fontStyle = parts[0] ? this.getFontStyle(parts[0], important) : ''; - const fontVariant = parts[1] ? 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 = fontSizeValue ? this.getFontSize(fontSizeValue, important) : ''; - const lineHeight = lineHeightValue ? this.getLineHeight(lineHeightValue, important) : ''; - const fontFamily = parts[5] ? this.getFontFamily(parts.slice(5).join(' '), important) : ''; + const parts = value.replace(/ *\/ */g, '/').split(/ +/); - const properties = {}; + for (let i = 0, max = parts.length; i < max; i++) { + const part = parts[i]; + if (part.includes('/')) { + const [size, height] = part.split('/'); + const fontSize = this.getFontSize(size, important); + const lineHeight = this.getLineHeight(height, important); + + if (!fontSize || !lineHeight) { + return null; + } - 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); + Object.assign(properties, fontSize, lineHeight); + } else { + const fontStyle = this.getFontStyle(part, important); + const fontVariant = this.getFontVariant(part, important); + const fontWeight = this.getFontWeight(part, important); + const fontSize = this.getFontSize(part, important); + const fontStretch = this.getFontStretch(part, important); + + if (fontStyle) { + Object.assign(properties, fontStyle); + } else if (fontVariant) { + Object.assign(properties, fontVariant); + } else if (fontWeight) { + Object.assign(properties, fontWeight); + } else if (fontSize) { + Object.assign(properties, fontSize); + } else if (fontStretch) { + Object.assign(properties, fontStretch); + } else { + const fontFamilyValue = parts.slice(i).join(' '); + const fontFamily = this.getFontFamily(fontFamilyValue, important); + if (!fontFamily) { + return null; + } + Object.assign(properties, fontFamily); + } + } } - return fontSize && - fontFamily && - fontStyle !== null && - fontVariant !== null && - fontWeight !== null && - fontStretch !== null && - lineHeight !== null - ? properties - : null; + return properties; } /** @@ -1837,7 +2192,7 @@ export default class CSSStyleDeclarationPropertySetParser { [key: string]: ICSSStyleDeclarationPropertyValue; } { const lowerValue = value.toLowerCase(); - if (FONT_STYLE.includes(lowerValue)) { + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || FONT_STYLE.includes(lowerValue)) { return { 'font-style': { value: lowerValue, important } }; } const parts = value.split(/ +/); @@ -1862,7 +2217,9 @@ export default class CSSStyleDeclarationPropertySetParser { [key: string]: ICSSStyleDeclarationPropertyValue; } { const lowerValue = value.toLowerCase(); - return lowerValue === 'normal' || lowerValue === 'small-caps' + return CSSStyleDeclarationValueParser.getGlobal(lowerValue) || + lowerValue === 'normal' || + lowerValue === 'small-caps' ? { 'font-variant': { value: lowerValue, important } } : null; } @@ -1881,7 +2238,7 @@ export default class CSSStyleDeclarationPropertySetParser { [key: string]: ICSSStyleDeclarationPropertyValue; } { const lowerValue = value.toLowerCase(); - if (FONT_STRETCH.includes(lowerValue)) { + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || FONT_STRETCH.includes(lowerValue)) { return { 'font-stretch': { value: lowerValue, important } }; } const percentage = CSSStyleDeclarationValueParser.getPercentage(value); @@ -1902,7 +2259,7 @@ export default class CSSStyleDeclarationPropertySetParser { [key: string]: ICSSStyleDeclarationPropertyValue; } { const lowerValue = value.toLowerCase(); - if (FONT_WEIGHT.includes(lowerValue)) { + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || FONT_WEIGHT.includes(lowerValue)) { return { 'font-weight': { value: lowerValue, important } }; } const integer = CSSStyleDeclarationValueParser.getInteger(value); @@ -1923,7 +2280,7 @@ export default class CSSStyleDeclarationPropertySetParser { [key: string]: ICSSStyleDeclarationPropertyValue; } { const lowerValue = value.toLowerCase(); - if (FONT_SIZE.includes(lowerValue)) { + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || FONT_SIZE.includes(lowerValue)) { return { 'font-size': { value: lowerValue, important } }; } const measurement = CSSStyleDeclarationValueParser.getMeasurement(value); @@ -1943,8 +2300,9 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - if (value.toLowerCase() === 'normal') { - return { 'line-height': { value: 'normal', important } }; + const lowerValue = value.toLowerCase(); + if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || lowerValue === 'normal') { + return { 'line-height': { value: lowerValue, important } }; } const lineHeight = CSSStyleDeclarationValueParser.getFloat(value) || @@ -1965,10 +2323,22 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); + + if (globalValue) { + return { 'font-family': { value: globalValue, important } }; + } + const parts = value.split(','); let parsedValue = ''; for (let i = 0, max = parts.length; i < max; i++) { - const trimmedPart = parts[i].trim().replace(/[']/g, '"'); + const trimmedPart = parts[i].trim().replace(/'/g, '"'); + if ( + trimmedPart.includes(' ') && + (trimmedPart[0] !== '"' || trimmedPart[trimmedPart.length - 1] !== "'") + ) { + return null; + } if (!trimmedPart) { return null; } @@ -1977,9 +2347,11 @@ export default class CSSStyleDeclarationPropertySetParser { } parsedValue += trimmedPart; } + if (!parsedValue) { return null; } + return { 'font-family': { important, diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts index cb3b5b8d5..84eff8a2b 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts @@ -11,6 +11,8 @@ const GRADIENT_REGEXP = /^(repeating-linear|linear|radial|repeating-radial|conic|repeating-conic)-gradient\([^)]+\)$/; const GLOBALS = ['inherit', 'initial', 'unset', 'revert']; const COLORS = [ + 'currentcolor', + 'transparent', 'silver', 'gray', 'white', @@ -352,4 +354,15 @@ export default class CSSStyleDeclarationValueParser { const lowerValue = value.toLowerCase(); return GLOBALS.includes(lowerValue) ? lowerValue : null; } + + /** + * Returns global. + * + * @param value Value. + * @returns Parsed value. + */ + public static getNonGlobalOrInitial(value: string): string { + const global = this.getGlobal(value); + return !global || global === 'initial' ? value : null; + } } From 714982177479b96e2872aba213e03975ef64d3cb Mon Sep 17 00:00:00 2001 From: David Ortner Date: Wed, 7 Sep 2022 00:56:18 +0200 Subject: [PATCH 34/84] #344@trivial: Continues on CSSStyleDeclaration. --- .../CSSStyleDeclarationPropertyGetParser.ts | 664 ++++++++++++------ .../CSSStyleDeclarationPropertyManager.ts | 8 + .../CSSStyleDeclarationPropertySetParser.ts | 29 +- .../CSSStyleDeclarationValueParser.ts | 18 +- 4 files changed, 503 insertions(+), 216 deletions(-) diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts index 81fa178b1..a6991d33f 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts @@ -14,24 +14,10 @@ export default class CSSStyleDeclarationPropertyGetParser { public static getMargin(properties: { [k: string]: ICSSStyleDeclarationPropertyValue; }): ICSSStyleDeclarationPropertyValue { - if ( - CSSStyleDeclarationValueParser.getGlobal(properties['margin-top']?.value) || - CSSStyleDeclarationValueParser.getGlobal(properties['margin-right']?.value) || - CSSStyleDeclarationValueParser.getGlobal(properties['margin-bottom']?.value) || - CSSStyleDeclarationValueParser.getGlobal(properties['margin-left']?.value) - ) { - return null; - } - - return { - important: ![ - properties['margin-top'].important, - properties['margin-right'].important, - properties['margin-bottom'].important, - properties['margin-left'].important - ].some((important) => important === false), - value: `${properties['margin-top'].value} ${properties['margin-right'].value} ${properties['margin-bottom'].value} ${properties['margin-left'].value}` - }; + return this.getPaddingLikeProperty( + ['margin-top', 'margin-right', 'margin-bottom', 'margin-left'], + properties + ); } /** @@ -43,24 +29,10 @@ export default class CSSStyleDeclarationPropertyGetParser { public static getPadding(properties: { [k: string]: ICSSStyleDeclarationPropertyValue; }): ICSSStyleDeclarationPropertyValue { - if ( - CSSStyleDeclarationValueParser.getGlobal(properties['padding-top']?.value) || - CSSStyleDeclarationValueParser.getGlobal(properties['padding-right']?.value) || - CSSStyleDeclarationValueParser.getGlobal(properties['padding-bottom']?.value) || - CSSStyleDeclarationValueParser.getGlobal(properties['padding-left']?.value) - ) { - return null; - } - - return { - important: ![ - properties['padding-top'].important, - properties['padding-right'].important, - properties['padding-bottom'].important, - properties['padding-left'].important - ].some((important) => important === false), - value: `${properties['padding-top'].value} ${properties['padding-right'].value} ${properties['padding-bottom'].value} ${properties['padding-left'].value}` - }; + return this.getPaddingLikeProperty( + ['padding-top', 'padding-right', 'padding-bottom', 'padding-left'], + properties + ); } /** @@ -89,39 +61,82 @@ export default class CSSStyleDeclarationPropertyGetParser { !properties['border-image-slice']?.value || !properties['border-image-width']?.value || !properties['border-image-outset']?.value || - !properties['border-image-repeat']?.value || - !CSSStyleDeclarationValueParser.getNonGlobalOrInitial(properties['border-top-width'].value) || - !CSSStyleDeclarationValueParser.getNonGlobalOrInitial(properties['border-top-style'].value) || - !CSSStyleDeclarationValueParser.getNonGlobalOrInitial(properties['border-top-color'].value) + !properties['border-image-repeat']?.value ) { return null; } - const values = [properties['border-top-width'].value]; + const 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 && + properties['border-image-source'].important && + properties['border-image-slice'].important && + properties['border-image-width'].important && + properties['border-image-outset'].important && + properties['border-image-repeat'].important; + + if ( + CSSStyleDeclarationValueParser.getGlobalExceptInitial(properties['border-top-width'].value) || + CSSStyleDeclarationValueParser.getGlobalExceptInitial(properties['border-top-style'].value) || + CSSStyleDeclarationValueParser.getGlobalExceptInitial(properties['border-top-color'].value) || + CSSStyleDeclarationValueParser.getGlobalExceptInitial( + properties['border-image-source'].value + ) || + CSSStyleDeclarationValueParser.getGlobalExceptInitial( + properties['border-image-slice'].value + ) || + CSSStyleDeclarationValueParser.getGlobalExceptInitial( + properties['border-image-width'].value + ) || + CSSStyleDeclarationValueParser.getGlobalExceptInitial( + properties['border-image-outset'].value + ) || + CSSStyleDeclarationValueParser.getGlobalExceptInitial(properties['border-image-repeat'].value) + ) { + if ( + properties['border-top-width'].value !== properties['border-top-style'].value || + properties['border-top-width'].value !== properties['border-top-color'].value || + properties['border-top-width'].value !== properties['border-image-source'].value || + properties['border-top-width'].value !== properties['border-image-slice'].value || + properties['border-top-width'].value !== properties['border-image-width'].value || + properties['border-top-width'].value !== properties['border-image-outset'].value || + properties['border-top-width'].value !== properties['border-image-repeat'].value + ) { + return null; + } + + return { + important, + value: properties['border-top-width'].value + }; + } + + const values = []; + + if (!CSSStyleDeclarationValueParser.getInitial(properties['border-top-width'].value)) { + values.push(properties['border-top-width'].value); + } - if (properties['border-top-style'].value !== 'initial') { + if (!CSSStyleDeclarationValueParser.getInitial(properties['border-top-style'].value)) { values.push(properties['border-top-style'].value); } - if (properties['border-top-color'].value !== 'initial') { + if (!CSSStyleDeclarationValueParser.getInitial(properties['border-top-color'].value)) { values.push(properties['border-top-color'].value); } 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), + important, value: values.join(' ') }; } @@ -135,24 +150,7 @@ export default class CSSStyleDeclarationPropertyGetParser { public static getBorderTop(properties: { [k: string]: ICSSStyleDeclarationPropertyValue; }): ICSSStyleDeclarationPropertyValue { - if (!properties['border-top-width']?.value) { - return null; - } - const values = [properties['border-top-width'].value]; - if (properties['border-top-style']?.value) { - values.push(properties['border-top-style'].value); - } - if (properties['border-top-color']?.value) { - values.push(properties['border-top-color'].value); - } - return { - important: ![ - properties['border-top-width']?.important, - properties['border-top-style']?.important, - properties['border-top-color']?.important - ].some((important) => important === false), - value: values.join(' ') - }; + return this.getBorderTopRightBottomLeft('top', properties); } /** @@ -164,24 +162,7 @@ export default class CSSStyleDeclarationPropertyGetParser { public static getBorderRight(properties: { [k: string]: ICSSStyleDeclarationPropertyValue; }): ICSSStyleDeclarationPropertyValue { - if (!properties['border-right-width']?.value) { - return null; - } - const values = [properties['border-right-width'].value]; - if (properties['border-right-style']?.value) { - values.push(properties['border-right-style'].value); - } - if (properties['border-right-color']?.value) { - values.push(properties['border-right-color'].value); - } - return { - important: ![ - properties['border-right-width']?.important, - properties['border-right-style']?.important, - properties['border-right-color']?.important - ].some((important) => important === false), - value: values.join(' ') - }; + return this.getBorderTopRightBottomLeft('right', properties); } /** @@ -193,24 +174,7 @@ export default class CSSStyleDeclarationPropertyGetParser { public static getBorderBottom(properties: { [k: string]: ICSSStyleDeclarationPropertyValue; }): ICSSStyleDeclarationPropertyValue { - if (!properties['border-bottom-width']?.value) { - return null; - } - const values = [properties['border-bottom-width'].value]; - if (properties['border-bottom-style']?.value) { - values.push(properties['border-bottom-style'].value); - } - if (properties['border-bottom-color']?.value) { - values.push(properties['border-bottom-color'].value); - } - return { - important: ![ - properties['border-bottom-width']?.important, - properties['border-bottom-style']?.important, - properties['border-bottom-color']?.important - ].some((important) => important === false), - value: values.join(' ') - }; + return this.getBorderTopRightBottomLeft('bottom', properties); } /** @@ -222,24 +186,7 @@ export default class CSSStyleDeclarationPropertyGetParser { public static getBorderLeft(properties: { [k: string]: ICSSStyleDeclarationPropertyValue; }): ICSSStyleDeclarationPropertyValue { - if (!properties['border-left-width']?.value) { - return null; - } - const values = [properties['border-left-width'].value]; - if (properties['border-left-style']?.value) { - values.push(properties['border-bottom-style'].value); - } - if (properties['border-left-color']?.value) { - values.push(properties['border-left-color'].value); - } - return { - important: ![ - properties['border-left-width']?.important, - properties['border-left-style']?.important, - properties['border-left-color']?.important - ].some((important) => important === false), - value: values.join(' ') - }; + return this.getBorderTopRightBottomLeft('left', properties); } /** @@ -260,12 +207,11 @@ export default class CSSStyleDeclarationPropertyGetParser { 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), + important: + properties['border-top-color'].important && + properties['border-right-color'].important && + properties['border-bottom-color'].important && + properties['border-left-color'].important, value: properties['border-top-color'].value }; } @@ -288,12 +234,11 @@ export default class CSSStyleDeclarationPropertyGetParser { 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), + important: + properties['border-top-width'].important && + properties['border-right-width'].important && + properties['border-bottom-width'].important && + properties['border-left-width'].important, value: properties['border-top-width'].value }; } @@ -316,12 +261,11 @@ export default class CSSStyleDeclarationPropertyGetParser { 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), + important: + properties['border-top-style'].important && + properties['border-right-style'].important && + properties['border-bottom-style'].important && + properties['border-left-style'].important, value: properties['border-top-style'].value }; } @@ -334,24 +278,65 @@ export default class CSSStyleDeclarationPropertyGetParser { */ public static getBorderRadius(properties: { [k: string]: ICSSStyleDeclarationPropertyValue; + }): ICSSStyleDeclarationPropertyValue { + return this.getPaddingLikeProperty( + [ + 'border-top-left-radius', + 'border-top-right-radius', + 'border-bottom-right-radius', + 'border-bottom-left-radius' + ], + properties + ); + } + + /** + * + */ + public static getBorderImage(properties: { + [k: string]: ICSSStyleDeclarationPropertyValue; }): ICSSStyleDeclarationPropertyValue { if ( - CSSStyleDeclarationValueParser.getGlobal(properties['border-top-left-radius']?.value) || - CSSStyleDeclarationValueParser.getGlobal(properties['border-top-right-radius']?.value) || - CSSStyleDeclarationValueParser.getGlobal(properties['border-bottom-right-radius']?.value) || - CSSStyleDeclarationValueParser.getGlobal(properties['border-bottom-left-radius']?.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; } + const important = + properties['border-image-source'].important && + properties['border-image-slice'].important && + properties['border-image-width'].important && + properties['border-image-outset'].important && + properties['border-image-repeat'].important; + + if ( + CSSStyleDeclarationValueParser.getGlobal(properties['border-image-source'].value) || + CSSStyleDeclarationValueParser.getGlobal(properties['border-image-slice'].value) || + CSSStyleDeclarationValueParser.getGlobal(properties['border-image-width'].value) || + CSSStyleDeclarationValueParser.getGlobal(properties['border-image-outset'].value) || + CSSStyleDeclarationValueParser.getGlobal(properties['border-image-repeat'].value) + ) { + if ( + properties['border-image-source'].value !== properties['border-image-slice'].value || + properties['border-image-source'].value !== properties['border-image-width'].value || + properties['border-image-source'].value !== properties['border-image-outset'].value || + properties['border-image-source'].value !== properties['border-image-repeat'].value + ) { + return null; + } + return { + important, + value: properties['border-image-source'].value + }; + } + return { - important: ![ - properties['border-top-left-radius'].important, - properties['border-top-right-radius'].important, - properties['border-bottom-right-radius'].important, - properties['margin-left'].important - ].some((important) => important === false), - value: `${properties['border-top-left-radius'].value} ${properties['border-top-right-radius'].value} ${properties['border-bottom-right-radius'].value} ${properties['border-bottom-left-radius'].value}` + important, + 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}` }; } @@ -364,37 +349,151 @@ export default class CSSStyleDeclarationPropertyGetParser { public static getBackground(properties: { [k: string]: ICSSStyleDeclarationPropertyValue; }): ICSSStyleDeclarationPropertyValue { - if (!properties['background-color']?.value && !properties['background-image']?.value) { + if ( + !properties['background-image']?.value || + !properties['background-repeat']?.value || + !properties['background-attachment']?.value || + !properties['background-position-x']?.value || + !properties['background-position-y']?.value || + !properties['background-color']?.value || + !properties['background-size']?.value || + !properties['background-origin']?.value || + !properties['background-clip']?.value + ) { return null; } - const values = []; - if (properties['background-color']?.value) { - values.push(properties['background-color'].value); + + const important = + properties['background-image'].important && + properties['background-repeat'].important && + properties['background-attachment'].important && + properties['background-position-x'].important && + properties['background-position-y'].important && + properties['background-color'].important && + properties['background-size'].important && + properties['background-origin'].important && + properties['background-clip'].important; + + if ( + CSSStyleDeclarationValueParser.getGlobalExceptInitial(properties['background-image'].value) || + CSSStyleDeclarationValueParser.getGlobalExceptInitial( + properties['background-repeat'].value + ) || + CSSStyleDeclarationValueParser.getGlobalExceptInitial( + properties['background-attachment'].value + ) || + CSSStyleDeclarationValueParser.getGlobalExceptInitial( + properties['background-position-x'].value + ) || + CSSStyleDeclarationValueParser.getGlobalExceptInitial( + properties['background-position-y'].value + ) || + CSSStyleDeclarationValueParser.getGlobalExceptInitial(properties['background-color'].value) || + CSSStyleDeclarationValueParser.getGlobalExceptInitial(properties['background-size'].value) || + CSSStyleDeclarationValueParser.getGlobalExceptInitial( + properties['background-origin'].value + ) || + CSSStyleDeclarationValueParser.getGlobalExceptInitial(properties['background-clip'].value) + ) { + if ( + properties['background-image'].value !== properties['background-repeat'].value || + properties['background-image'].value !== properties['background-attachment'].value || + properties['background-image'].value !== properties['background-position-x'].value || + properties['background-image'].value !== properties['background-position-y'].value || + properties['background-image'].value !== properties['background-color'].value || + properties['background-image'].value !== properties['background-size'].value || + properties['background-image'].value !== properties['background-origin'].value || + properties['background-image'].value !== properties['background-clip'].value + ) { + return null; + } + + return { + important, + value: properties['background-image'].value + }; } - if (properties['background-image']?.value) { + + const values = []; + + if (!CSSStyleDeclarationValueParser.getInitial(properties['background-image'].value)) { values.push(properties['background-image'].value); } - if (properties['background-repeat']?.value) { + + if (!CSSStyleDeclarationValueParser.getInitial(properties['background-repeat'].value)) { values.push(properties['background-repeat'].value); } - if (properties['background-attachment']?.value) { + + if (!CSSStyleDeclarationValueParser.getInitial(properties['background-attachment'].value)) { values.push(properties['background-attachment'].value); } - if (properties['background-position']?.value) { - values.push(properties['background-position'].value); + + if ( + !CSSStyleDeclarationValueParser.getInitial(properties['background-position-x'].value) && + !CSSStyleDeclarationValueParser.getInitial(properties['background-position-y'].value) && + !CSSStyleDeclarationValueParser.getInitial(properties['background-position-size'].value) + ) { + values.push( + `${properties['background-position-x'].value} ${properties['background-position-y'].value} / ${properties['background-position-size'].value}` + ); + } else if ( + !CSSStyleDeclarationValueParser.getInitial(properties['background-position-x'].value) && + !CSSStyleDeclarationValueParser.getInitial(properties['background-position-y'].value) + ) { + values.push( + `${properties['background-position-x'].value} ${properties['background-position-y'].value}` + ); + } + + if (!CSSStyleDeclarationValueParser.getInitial(properties['background-color'].value)) { + values.push(properties['background-color'].value); } + 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), + important, value: values.join(' ') }; } + /** + * Returns background position. + * + * @param properties Properties. + * @returns Property value + */ + public static getBackgroundPosition(properties: { + [k: string]: ICSSStyleDeclarationPropertyValue; + }): ICSSStyleDeclarationPropertyValue { + if ( + !properties['background-position-x']?.value || + !properties['background-position-y']?.value + ) { + return null; + } + + const important = + properties['background-position-x'].important && + properties['background-position-y'].important; + if ( + CSSStyleDeclarationValueParser.getGlobal(properties['background-position-x'].value) || + CSSStyleDeclarationValueParser.getGlobal(properties['background-position-y'].value) + ) { + if (properties['background-position-x'].value !== properties['background-position-y'].value) { + return null; + } + + return { + important, + value: properties['background-position-x'].value + }; + } + + return { + important, + value: `${properties['background-position-x'].value} ${properties['background-position-y'].value}` + }; + } + /** * Returns flex. * @@ -411,12 +510,32 @@ export default class CSSStyleDeclarationPropertyGetParser { ) { return null; } + + const important = + properties['flex-grow'].important && + properties['flex-shrink'].important && + properties['flex-basis'].important; + + if ( + CSSStyleDeclarationValueParser.getGlobal(properties['flex-grow'].value) || + CSSStyleDeclarationValueParser.getGlobal(properties['flex-shrink'].value) || + CSSStyleDeclarationValueParser.getGlobal(properties['flex-basis'].value) + ) { + if ( + properties['flex-grow'].value !== properties['flex-shrink'].value || + properties['flex-grow'].value !== properties['flex-basis'].value + ) { + return null; + } + + return { + important, + value: properties['flex-grow'].value + }; + } + return { - important: ![ - properties['flex-grow']?.important, - properties['flex-shrink']?.important, - properties['flex-basis']?.important - ].some((important) => important === false), + important, value: `${properties['flex-grow'].value} ${properties['flex-shrink'].value} ${properties['flex-basis'].value}` }; } @@ -430,46 +549,193 @@ export default class CSSStyleDeclarationPropertyGetParser { public static getFont(properties: { [k: string]: ICSSStyleDeclarationPropertyValue; }): ICSSStyleDeclarationPropertyValue { - if (!properties['font-family']?.value || !properties['font-size']?.value) { + if ( + !properties['font-size']?.value || + !properties['font-family']?.value || + !properties['font-weight']?.value || + !properties['font-style']?.value || + !properties['font-variant']?.value || + !properties['font-stretch']?.value || + !properties['line-height']?.value + ) { return null; } - const sizeAndLineHeight = [properties['font-size'].value]; - if (properties['line-height']?.value) { - sizeAndLineHeight.push(properties['line-height'].value); + const important = + properties['font-size'].important && + properties['font-family'].important && + properties['font-weight'].important && + properties['font-style'].important && + properties['font-variant'].important && + properties['font-stretch'].important && + properties['line-height'].important; + + if ( + CSSStyleDeclarationValueParser.getGlobal(properties['font-size'].value) || + CSSStyleDeclarationValueParser.getGlobal(properties['font-family'].value) || + CSSStyleDeclarationValueParser.getGlobal(properties['font-weight'].value) || + CSSStyleDeclarationValueParser.getGlobal(properties['font-style'].value) || + CSSStyleDeclarationValueParser.getGlobal(properties['font-variant'].value) || + CSSStyleDeclarationValueParser.getGlobal(properties['font-stretch'].value) || + CSSStyleDeclarationValueParser.getGlobal(properties['line-height'].value) + ) { + if ( + properties['font-size'].value !== properties['font-family'].value || + properties['font-size'].value !== properties['font-weight'].value || + properties['font-size'].value !== properties['font-style'].value || + properties['font-size'].value !== properties['font-variant'].value || + properties['font-size'].value !== properties['font-stretch'].value || + properties['font-size'].value !== properties['line-height'].value + ) { + return null; + } + + return { + important, + value: properties['font-size'].value + }; } const values = []; - if (properties['font-style']?.value) { + + if (properties['font-style'].value !== 'normal') { values.push(properties['font-style'].value); } - if (properties['font-variant']?.value) { + if (properties['font-variant'].value !== 'normal') { values.push(properties['font-variant'].value); } - if (properties['font-weight']?.value) { + if (properties['font-weight'].value !== 'normal') { values.push(properties['font-weight'].value); } - if (properties['font-stretch']?.value) { + if (properties['font-stretch'].value !== 'normal') { values.push(properties['font-stretch'].value); } - values.push(sizeAndLineHeight.join('/')); + if (properties['line-height'].value !== 'normal') { + values.push(`${properties['font-size'].value} / ${properties['line-height'].value}`); + } else { + values.push(properties['font-size'].value); + } + + values.push(properties['font-family'].value); - if (properties['font-family']?.value) { - values.push(properties['font-family'].value); + return { + important, + value: values.join(' ') + }; + } + + /** + * Returns border. + * + * @param properties Properties. + * @param position + * @returns Property value + */ + private static getBorderTopRightBottomLeft( + position: 'top' | 'right' | 'bottom' | 'left', + properties: { + [k: string]: ICSSStyleDeclarationPropertyValue; + } + ): ICSSStyleDeclarationPropertyValue { + if ( + !properties[`border-${position}-width`]?.value || + !properties[`border-${position}-style`]?.value || + !properties[`border-${position}-color`]?.value + ) { + return null; + } + + const important = + properties[`border-${position}-width`].important && + properties[`border-${position}-style`].important && + properties[`border-${position}-color`].important; + + if ( + CSSStyleDeclarationValueParser.getGlobalExceptInitial( + properties[`border-${position}-width`].value + ) && + properties[`border-${position}-width`].value === + properties[`border-${position}-style`].value && + properties[`border-${position}-width`].value === properties[`border-${position}-color`].value + ) { + return { + important, + value: properties[`border-${position}-width`].value + }; + } + + const values = []; + + if (!CSSStyleDeclarationValueParser.getInitial(properties[`border-${position}-width`].value)) { + values.push(properties[`border-${position}-width`].value); + } + if (!CSSStyleDeclarationValueParser.getInitial(properties[`border-${position}-style`]?.value)) { + values.push(properties[`border-${position}-style`].value); + } + if (!CSSStyleDeclarationValueParser.getInitial(properties[`border-${position}-color`]?.value)) { + values.push(properties[`border-${position}-color`].value); } 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), + important, value: values.join(' ') }; } + + /** + * Returns a padding like property. + * + * @param properties Properties. + * @param position + * @param propertyNames + * @returns Property value + */ + private static getPaddingLikeProperty( + propertyNames: [string, string, string, string], + properties: { + [k: string]: ICSSStyleDeclarationPropertyValue; + } + ): ICSSStyleDeclarationPropertyValue { + if ( + !properties[propertyNames[0]]?.value || + !properties[propertyNames[1]]?.value || + !properties[propertyNames[2]]?.value || + !properties[propertyNames[3]]?.value + ) { + return null; + } + + const important = + properties[propertyNames[0]].important && + properties[propertyNames[1]].important && + properties[propertyNames[2]].important && + properties[propertyNames[3]].important; + + if ( + CSSStyleDeclarationValueParser.getGlobal(properties[propertyNames[0]].value) || + CSSStyleDeclarationValueParser.getGlobal(properties[propertyNames[1]].value) || + CSSStyleDeclarationValueParser.getGlobal(properties[propertyNames[2]].value) || + CSSStyleDeclarationValueParser.getGlobal(properties[propertyNames[3]].value) + ) { + if ( + properties[propertyNames[0]].value !== properties[propertyNames[1]].value || + properties[propertyNames[0]].value !== properties[propertyNames[2]].value || + properties[propertyNames[0]].value !== properties[propertyNames[3]].value + ) { + return null; + } + return { + important, + value: properties[propertyNames[0]].value + }; + } + + return { + important, + value: `${properties[propertyNames[0]].value} ${properties[propertyNames[1]].value} ${ + properties[propertyNames[2]].value + } ${properties[propertyNames[3]].value}` + }; + } } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts index 25dc18513..be2230b69 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts @@ -75,8 +75,12 @@ export default class CSSStyleDeclarationPropertyManager { return CSSStyleDeclarationPropertyGetParser.getBorderWidth(this.properties); case 'border-radius': return CSSStyleDeclarationPropertyGetParser.getBorderRadius(this.properties); + case 'border-image': + return CSSStyleDeclarationPropertyGetParser.getBorderImage(this.properties); case 'background': return CSSStyleDeclarationPropertyGetParser.getBackground(this.properties); + case 'background-position': + return CSSStyleDeclarationPropertyGetParser.getBackgroundPosition(this.properties); case 'flex': return CSSStyleDeclarationPropertyGetParser.getFlex(this.properties); case 'font': @@ -168,6 +172,10 @@ export default class CSSStyleDeclarationPropertyManager { delete this.properties['background-attachment']; delete this.properties['background-position']; break; + case 'background-position': + delete this.properties['background-position-x']; + delete this.properties['background-position-y']; + break; case 'flex': delete this.properties['flex-grow']; delete this.properties['flex-shrink']; diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts index e1d7092b2..88d8b048b 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts @@ -1619,14 +1619,14 @@ export default class CSSStyleDeclarationPropertySetParser { } const properties = { - ...this.getBackgroundImage('none', important), - ...this.getBackgroundPosition('0% 0%', important), - ...this.getBackgroundSize('auto auto', important), - ...this.getBackgroundRepeat('repeat', important), - ...this.getBackgroundAttachment('scroll', important), - ...this.getBackgroundOrigin('padding-box', important), - ...this.getBackgroundClip('border-box', important), - ...this.getBackgroundColor('transparent', important) + ...this.getBackgroundImage('initial', important), + ...this.getBackgroundPosition('initial', important), + ...this.getBackgroundSize('initial', important), + ...this.getBackgroundRepeat('initial', important), + ...this.getBackgroundAttachment('initial', important), + ...this.getBackgroundOrigin('initial', important), + ...this.getBackgroundClip('initial', important), + ...this.getBackgroundColor('initial', important) }; const parts = value.replace(/[ ]*\/[ ]*/g, '/').split(/ +/); @@ -1634,7 +1634,9 @@ export default class CSSStyleDeclarationPropertySetParser { for (const part of parts) { if (part.includes('/')) { const [position, size] = part.split('/'); - const backgroundPosition = this.getBackgroundImage(position, important); + const backgroundPosition = properties['background-position-x'] + ? this.getBackgroundPositionY(position, important) + : this.getBackgroundPosition(position, important); const backgroundSize = this.getBackgroundSize(size, important); if (!backgroundPosition || !backgroundSize) { @@ -1646,14 +1648,14 @@ export default class CSSStyleDeclarationPropertySetParser { const backgroundImage = this.getBackgroundImage(part, important); const backgroundRepeat = this.getBackgroundRepeat(part, important); const backgroundAttachment = this.getBackgroundAttachment(part, important); - const backgroundOrigin = this.getBackgroundOrigin(part, important); + const backgroundPosition = this.getBackgroundPosition(part, important); const backgroundColor = this.getBackgroundColor(part, important); if ( !backgroundImage && !backgroundRepeat && !backgroundAttachment && - !backgroundOrigin && + !backgroundPosition && !backgroundColor ) { return null; @@ -1664,6 +1666,7 @@ export default class CSSStyleDeclarationPropertySetParser { backgroundImage, backgroundRepeat, backgroundAttachment, + backgroundPosition, backgroundColor ); } @@ -1909,7 +1912,7 @@ export default class CSSStyleDeclarationPropertySetParser { const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue)) { - return { 'background-position': { value: lowerValue, important } }; + return { 'background-position-x': { value: lowerValue, important } }; } const imageParts = lowerValue.split(','); @@ -1959,7 +1962,7 @@ export default class CSSStyleDeclarationPropertySetParser { const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue)) { - return { 'background-position': { value: lowerValue, important } }; + return { 'background-position-y': { value: lowerValue, important } }; } const imageParts = lowerValue.split(','); diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts index 84eff8a2b..6d5bb7eca 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts @@ -344,6 +344,16 @@ export default class CSSStyleDeclarationValueParser { return value; } + /** + * Returns global initial value. + * + * @param value Value. + * @returns Parsed value. + */ + public static getInitial(value: string): string { + return value.toLowerCase() === 'initial' ? 'initial' : null; + } + /** * Returns global. * @@ -356,13 +366,13 @@ export default class CSSStyleDeclarationValueParser { } /** - * Returns global. + * Returns global, unless it is not set to 'initial' as it is sometimes treated different. * * @param value Value. * @returns Parsed value. */ - public static getNonGlobalOrInitial(value: string): string { - const global = this.getGlobal(value); - return !global || global === 'initial' ? value : null; + public static getGlobalExceptInitial(value: string): string { + const lowerValue = value.toLowerCase(); + return lowerValue !== 'initial' && GLOBALS.includes(lowerValue) ? lowerValue : null; } } From a321e544503af68cd817a68e45e4cc4ea4525043 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Wed, 7 Sep 2022 16:52:39 +0200 Subject: [PATCH 35/84] #344@trivial: Continue on CSSStyleDeclaration. --- .../CSSStyleDeclarationPropertySetParser.ts | 15 +++++++++++---- .../css/declaration/CSSStyleDeclaration.test.ts | 1 + 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts index 88d8b048b..03f6817b1 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts @@ -664,7 +664,10 @@ export default class CSSStyleDeclarationPropertySetParser { const parts = lowerValue.split(/ +/); for (const part of parts) { - if (!CSSStyleDeclarationValueParser.getMeasurementOrAuto(part)) { + if ( + !CSSStyleDeclarationValueParser.getInteger(part) && + !CSSStyleDeclarationValueParser.getMeasurementOrAuto(part) + ) { return null; } } @@ -1061,9 +1064,13 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const parts = value.split(/ +/); const topLeft = parts[0] ? this.getBorderTopLeftRadius(parts[0], important) : ''; - const topRight = parts[1] ? this.getBorderTopRightRadius(parts[1], important) : ''; - const bottomRight = parts[2] ? this.getBorderTopRightRadius(parts[2], important) : ''; - const bottomLeft = parts[3] ? this.getBorderTopRightRadius(parts[3], important) : ''; + const topRight = parts[1] ? this.getBorderTopRightRadius(parts[1] || parts[0], important) : ''; + const bottomRight = parts[2] + ? this.getBorderTopRightRadius(parts[2] || parts[0], important) + : ''; + const bottomLeft = parts[3] + ? this.getBorderTopRightRadius(parts[3] || parts[1] || parts[0], important) + : ''; if (topLeft && topRight !== null && bottomRight !== null && bottomLeft !== null) { return { diff --git a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts index a3091fd96..b2645d175 100644 --- a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts +++ b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts @@ -325,6 +325,7 @@ describe('CSSStyleDeclaration', () => { it('Returns a CSS property without element.', () => { const declaration = new CSSStyleDeclaration(); + debugger; declaration.cssText = `border: 2px solid green;border-radius: 2px;font-size: 12px;`; expect(declaration.getPropertyValue('border')).toBe('2px solid green'); From b8c2672c6cb19a80c3e639042517ac7d61a3fa1b Mon Sep 17 00:00:00 2001 From: David Ortner Date: Thu, 8 Sep 2022 00:56:37 +0200 Subject: [PATCH 36/84] #344@trivial: Continues on CSSStyleDeclaration. --- .../CSSStyleDeclarationPropertyGetParser.ts | 85 ++++------ .../CSSStyleDeclarationPropertyManager.ts | 90 ++++++++++- .../CSSStyleDeclarationPropertySetParser.ts | 150 +++++++++++------- .../declaration/CSSStyleDeclaration.test.ts | 77 +++++---- packages/happy-dom/test/window/Window.test.ts | 2 +- 5 files changed, 267 insertions(+), 137 deletions(-) diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts index a6991d33f..4916666bd 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts @@ -198,22 +198,10 @@ export default class CSSStyleDeclarationPropertyGetParser { 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, - value: properties['border-top-color'].value - }; + return this.getPaddingLikeProperty( + ['border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color'], + properties + ); } /** @@ -225,22 +213,10 @@ export default class CSSStyleDeclarationPropertyGetParser { 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, - value: properties['border-top-width'].value - }; + return this.getPaddingLikeProperty( + ['border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width'], + properties + ); } /** @@ -252,22 +228,10 @@ export default class CSSStyleDeclarationPropertyGetParser { 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, - value: properties['border-top-style'].value - }; + return this.getPaddingLikeProperty( + ['border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style'], + properties + ); } /** @@ -731,11 +695,30 @@ export default class CSSStyleDeclarationPropertyGetParser { }; } + const values = [properties[propertyNames[0]].value]; + + if ( + properties[propertyNames[1]].value !== properties[propertyNames[0]].value || + properties[propertyNames[2]].value !== properties[propertyNames[0]].value || + properties[propertyNames[3]].value !== properties[propertyNames[1]].value + ) { + values.push(properties[propertyNames[1]].value); + } + + if ( + properties[propertyNames[2]].value !== properties[propertyNames[0]].value || + properties[propertyNames[3]].value !== properties[propertyNames[1]].value + ) { + values.push(properties[propertyNames[2]].value); + } + + if (properties[propertyNames[3]].value !== properties[propertyNames[1]].value) { + values.push(properties[propertyNames[3]].value); + } + return { important, - value: `${properties[propertyNames[0]].value} ${properties[propertyNames[1]].value} ${ - properties[propertyNames[2]].value - } ${properties[propertyNames[3]].value}` + value: values.join(' ') }; } } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts index be2230b69..04f54e6f2 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts @@ -113,26 +113,51 @@ export default class CSSStyleDeclarationPropertyManager { delete this.properties['border-right-color']; delete this.properties['border-bottom-color']; delete this.properties['border-left-color']; + delete this.properties['border-image-source']; + delete this.properties['border-image-slice']; + delete this.properties['border-image-width']; + delete this.properties['border-image-outset']; + delete this.properties['border-image-repeat']; break; case 'border-left': delete this.properties['border-left-width']; delete this.properties['border-left-style']; delete this.properties['border-left-color']; + delete this.properties['border-image-source']; + delete this.properties['border-image-slice']; + delete this.properties['border-image-width']; + delete this.properties['border-image-outset']; + delete this.properties['border-image-repeat']; break; case 'border-bottom': delete this.properties['border-bottom-width']; delete this.properties['border-bottom-style']; delete this.properties['border-bottom-color']; + delete this.properties['border-image-source']; + delete this.properties['border-image-slice']; + delete this.properties['border-image-width']; + delete this.properties['border-image-outset']; + delete this.properties['border-image-repeat']; break; case 'border-right': delete this.properties['border-right-width']; delete this.properties['border-right-style']; delete this.properties['border-right-color']; + delete this.properties['border-image-source']; + delete this.properties['border-image-slice']; + delete this.properties['border-image-width']; + delete this.properties['border-image-outset']; + delete this.properties['border-image-repeat']; break; case 'border-top': delete this.properties['border-top-width']; delete this.properties['border-top-style']; delete this.properties['border-top-color']; + delete this.properties['border-image-source']; + delete this.properties['border-image-slice']; + delete this.properties['border-image-width']; + delete this.properties['border-image-outset']; + delete this.properties['border-image-repeat']; break; case 'border-width': delete this.properties['border-top-width']; @@ -170,7 +195,11 @@ export default class CSSStyleDeclarationPropertyManager { delete this.properties['background-image']; delete this.properties['background-repeat']; delete this.properties['background-attachment']; - delete this.properties['background-position']; + delete this.properties['background-position-x']; + delete this.properties['background-position-y']; + delete this.properties['background-size']; + delete this.properties['background-origin']; + delete this.properties['background-clip']; break; case 'background-position': delete this.properties['background-position-x']; @@ -499,10 +528,63 @@ export default class CSSStyleDeclarationPropertyManager { */ public toString(): string { const result = []; + const clone = this.clone(); + const properties = {}; - for (const name of Object.keys(this.definedPropertyNames)) { - const property = this.get(name); - result.push(`${name}: ${property.value}${property.important ? ' !important' : ''};`); + for (const fallbackNames of [ + ['margin'], + ['padding'], + ['border', ['border-width', 'border-style', 'border-color', 'border-image']], + ['border-radius'], + ['background', 'background-position'], + ['font'] + ]) { + for (const fallbackName of fallbackNames) { + if (Array.isArray(fallbackName)) { + let isMatch = false; + for (const childFallbackName of fallbackName) { + const property = clone.get(childFallbackName); + if (property) { + properties[childFallbackName] = property; + clone.remove(childFallbackName); + isMatch = true; + } + } + if (isMatch) { + break; + } + } else { + const property = clone.get(fallbackName); + if (property) { + properties[fallbackName] = property; + clone.remove(fallbackName); + break; + } + } + } + } + + for (const name of Object.keys(clone.properties)) { + properties[name] = clone.get(name); + } + + for (const definedPropertyName of Object.keys(this.definedPropertyNames)) { + const property = properties[definedPropertyName]; + if (property) { + result.push( + `${definedPropertyName}: ${property.value}${property.important ? ' !important' : ''};` + ); + delete properties[definedPropertyName]; + } + } + + for (const propertyName of Object.keys(properties)) { + const property = properties[propertyName]; + if (property) { + result.push( + `${propertyName}: ${property.value}${property.important ? ' !important' : ''};` + ); + } } return result.join(' '); diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts index 03f6817b1..0d002c1e7 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts @@ -349,7 +349,7 @@ export default class CSSStyleDeclarationPropertySetParser { ...this.getBorderWidth('initial', important), ...this.getBorderStyle('initial', important), ...this.getBorderColor('initial', important), - ...this.getBorderImage('none', important) + ...this.getBorderImage('initial', important) }; const parts = value.split(/ +/); @@ -578,7 +578,7 @@ export default class CSSStyleDeclarationPropertySetParser { return { 'border-image-source': { important, - value: 'none' + value: lowerValue } }; } @@ -1062,26 +1062,33 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const parts = value.split(/ +/); - const topLeft = parts[0] ? this.getBorderTopLeftRadius(parts[0], important) : ''; - const topRight = parts[1] ? this.getBorderTopRightRadius(parts[1] || parts[0], important) : ''; - const bottomRight = parts[2] - ? this.getBorderTopRightRadius(parts[2] || parts[0], important) - : ''; - const bottomLeft = parts[3] - ? this.getBorderTopRightRadius(parts[3] || parts[1] || parts[0], important) - : ''; - - if (topLeft && topRight !== null && bottomRight !== null && bottomLeft !== null) { + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); + + if (globalValue) { return { - ...topLeft, - ...topRight, - ...bottomRight, - ...bottomLeft + ...this.getBorderTopLeftRadius(globalValue, important), + ...this.getBorderTopRightRadius(globalValue, important), + ...this.getBorderTopRightRadius(globalValue, important), + ...this.getBorderTopRightRadius(globalValue, important) }; } - return null; + const parts = value.split(/ +/); + const topLeft = this.getBorderTopLeftRadius(parts[0], important); + const topRight = this.getBorderTopRightRadius(parts[1] || parts[0], important); + const bottomRight = this.getBorderBottomRightRadius(parts[2] || parts[0], important); + const bottomLeft = this.getBorderBottomLeftRadius(parts[3] || parts[1] || parts[0], important); + + if (!topLeft || !topRight || !bottomRight || !bottomLeft) { + return null; + } + + return { + ...topLeft, + ...topRight, + ...bottomRight, + ...bottomLeft + }; } /** @@ -1095,7 +1102,9 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const radius = CSSStyleDeclarationValueParser.getMeasurement(value); + const radius = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getMeasurement(value); return radius ? { 'border-top-left-radius': { important, value: radius } } : null; } @@ -1110,7 +1119,9 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const radius = CSSStyleDeclarationValueParser.getMeasurement(value); + const radius = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getMeasurement(value); return radius ? { 'border-top-right-radius': { important, value: radius } } : null; } @@ -1125,7 +1136,9 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const radius = CSSStyleDeclarationValueParser.getMeasurement(value); + const radius = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getMeasurement(value); return radius ? { 'border-bottom-right-radius': { important, value: radius } } : null; } @@ -1140,7 +1153,9 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { - const radius = CSSStyleDeclarationValueParser.getMeasurement(value); + const radius = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getMeasurement(value); return radius ? { 'border-bottom-left-radius': { important, value: radius } } : null; } @@ -1156,13 +1171,20 @@ export default class CSSStyleDeclarationPropertySetParser { important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); + + if (globalValue) { + return { + ...this.getBorderTopWidth(globalValue, important), + ...this.getBorderTopStyle(globalValue, important), + ...this.getBorderTopColor(globalValue, important), + ...this.getBorderImage(globalValue, important) + }; + } + const parts = value.split(/ +/); - const borderWidth = - globalValue || parts[0] ? this.getBorderTopWidth(globalValue || parts[0], important) : ''; - const borderStyle = - globalValue || parts[1] ? this.getBorderTopStyle(globalValue || parts[1], important) : ''; - const borderColor = - globalValue || parts[2] ? this.getBorderTopColor(globalValue || parts[2], important) : ''; + const borderWidth = parts[0] ? this.getBorderTopWidth(parts[0], important) : ''; + const borderStyle = parts[1] ? this.getBorderTopStyle(parts[1], important) : ''; + const borderColor = parts[2] ? this.getBorderTopColor(parts[2], important) : ''; if (borderWidth && borderStyle !== null && borderColor !== null) { return { @@ -1188,13 +1210,20 @@ export default class CSSStyleDeclarationPropertySetParser { important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); + + if (globalValue) { + return { + ...this.getBorderRightWidth(globalValue, important), + ...this.getBorderRightStyle(globalValue, important), + ...this.getBorderRightColor(globalValue, important), + ...this.getBorderImage(globalValue, important) + }; + } + const parts = value.split(/ +/); - const borderWidth = - globalValue || parts[0] ? this.getBorderRightWidth(globalValue || parts[0], important) : ''; - const borderStyle = - globalValue || parts[1] ? this.getBorderRightStyle(globalValue || parts[1], important) : ''; - const borderColor = - globalValue || parts[2] ? this.getBorderRightColor(globalValue || parts[2], important) : ''; + const borderWidth = parts[0] ? this.getBorderRightWidth(parts[0], important) : ''; + const borderStyle = parts[1] ? this.getBorderRightStyle(parts[1], important) : ''; + const borderColor = parts[2] ? this.getBorderRightColor(parts[2], important) : ''; if (borderWidth && borderStyle !== null && borderColor !== null) { return { @@ -1220,13 +1249,20 @@ export default class CSSStyleDeclarationPropertySetParser { important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); + + if (globalValue) { + return { + ...this.getBorderBottomWidth(globalValue, important), + ...this.getBorderBottomStyle(globalValue, important), + ...this.getBorderBottomColor(globalValue, important), + ...this.getBorderImage(globalValue, important) + }; + } + const parts = value.split(/ +/); - const borderWidth = - globalValue || parts[0] ? this.getBorderBottomWidth(globalValue || parts[0], important) : ''; - const borderStyle = - globalValue || parts[1] ? this.getBorderBottomStyle(globalValue || parts[1], important) : ''; - const borderColor = - globalValue || parts[2] ? this.getBorderBottomColor(globalValue || parts[2], important) : ''; + const borderWidth = parts[0] ? this.getBorderBottomWidth(parts[0], important) : ''; + const borderStyle = parts[1] ? this.getBorderBottomStyle(parts[1], important) : ''; + const borderColor = parts[2] ? this.getBorderBottomColor(parts[2], important) : ''; if (borderWidth && borderStyle !== null && borderColor !== null) { return { @@ -1252,13 +1288,20 @@ export default class CSSStyleDeclarationPropertySetParser { important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); + + if (globalValue) { + return { + ...this.getBorderLeftWidth(globalValue, important), + ...this.getBorderLeftStyle(globalValue, important), + ...this.getBorderLeftColor(globalValue, important), + ...this.getBorderImage(globalValue, important) + }; + } + const parts = value.split(/ +/); - const borderWidth = - globalValue || parts[0] ? this.getBorderLeftWidth(globalValue || parts[0], important) : ''; - const borderStyle = - globalValue || parts[1] ? this.getBorderLeftStyle(globalValue || parts[1], important) : ''; - const borderColor = - globalValue || parts[2] ? this.getBorderLeftColor(globalValue || parts[2], important) : ''; + const borderWidth = parts[0] ? this.getBorderLeftWidth(parts[0], important) : ''; + const borderStyle = parts[1] ? this.getBorderLeftStyle(parts[1], important) : ''; + const borderColor = parts[2] ? this.getBorderLeftColor(parts[2], important) : ''; if (borderWidth && borderStyle !== null && borderColor !== null) { return { @@ -1295,9 +1338,9 @@ export default class CSSStyleDeclarationPropertySetParser { const parts = value.split(/ +/); const top = this.getPaddingTop(parts[0], important); - const right = this.getPaddingRight(parts[1] || '0', important); - const bottom = this.getPaddingBottom(parts[2] || '0', important); - const left = this.getPaddingLeft(parts[3] || '0', important); + const right = this.getPaddingRight(parts[1] || parts[0], important); + const bottom = this.getPaddingBottom(parts[2] || parts[0], important); + const left = this.getPaddingLeft(parts[3] || parts[1] || parts[0], important); if (!top || !right || !bottom || !left) { return null; @@ -1403,9 +1446,9 @@ export default class CSSStyleDeclarationPropertySetParser { const parts = value.split(/ +/); const top = this.getMarginTop(parts[0], important); - const right = this.getMarginRight(parts[1] || '0', important); - const bottom = this.getMarginBottom(parts[2] || '0', important); - const left = this.getMarginLeft(parts[3] || '0', important); + const right = this.getMarginRight(parts[1] || parts[0], important); + const bottom = this.getMarginBottom(parts[2] || parts[0], important); + const left = this.getMarginLeft(parts[3] || parts[1] || parts[0], important); if (!top || !right || !bottom || !left) { return null; @@ -2181,6 +2224,7 @@ export default class CSSStyleDeclarationPropertySetParser { return null; } Object.assign(properties, fontFamily); + break; } } } @@ -2345,7 +2389,7 @@ export default class CSSStyleDeclarationPropertySetParser { const trimmedPart = parts[i].trim().replace(/'/g, '"'); if ( trimmedPart.includes(' ') && - (trimmedPart[0] !== '"' || trimmedPart[trimmedPart.length - 1] !== "'") + (trimmedPart[0] !== '"' || trimmedPart[trimmedPart.length - 1] !== '"') ) { return null; } diff --git a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts index b2645d175..ea6756d88 100644 --- a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts +++ b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts @@ -33,12 +33,17 @@ describe('CSSStyleDeclaration', () => { expect(declaration[9]).toBe('border-right-color'); expect(declaration[10]).toBe('border-bottom-color'); expect(declaration[11]).toBe('border-left-color'); - expect(declaration[12]).toBe('border-top-left-radius'); - expect(declaration[13]).toBe('border-top-right-radius'); - expect(declaration[14]).toBe('border-bottom-right-radius'); - expect(declaration[15]).toBe('border-bottom-left-radius'); - expect(declaration[16]).toBe('font-size'); - expect(declaration[17]).toBe(undefined); + expect(declaration[12]).toBe('border-image-source'); + expect(declaration[13]).toBe('border-image-slice'); + expect(declaration[14]).toBe('border-image-width'); + expect(declaration[15]).toBe('border-image-outset'); + expect(declaration[16]).toBe('border-image-repeat'); + expect(declaration[17]).toBe('border-top-left-radius'); + expect(declaration[18]).toBe('border-top-right-radius'); + expect(declaration[19]).toBe('border-bottom-right-radius'); + expect(declaration[20]).toBe('border-bottom-left-radius'); + expect(declaration[21]).toBe('font-size'); + expect(declaration[22]).toBe(undefined); }); it('Returns name of property without element.', () => { @@ -60,12 +65,17 @@ describe('CSSStyleDeclaration', () => { expect(declaration[9]).toBe('border-right-color'); expect(declaration[10]).toBe('border-bottom-color'); expect(declaration[11]).toBe('border-left-color'); - expect(declaration[12]).toBe('border-top-left-radius'); - expect(declaration[13]).toBe('border-top-right-radius'); - expect(declaration[14]).toBe('border-bottom-right-radius'); - expect(declaration[15]).toBe('border-bottom-left-radius'); - expect(declaration[16]).toBe('font-size'); - expect(declaration[17]).toBe(undefined); + expect(declaration[12]).toBe('border-image-source'); + expect(declaration[13]).toBe('border-image-slice'); + expect(declaration[14]).toBe('border-image-width'); + expect(declaration[15]).toBe('border-image-outset'); + expect(declaration[16]).toBe('border-image-repeat'); + expect(declaration[17]).toBe('border-top-left-radius'); + expect(declaration[18]).toBe('border-top-right-radius'); + expect(declaration[19]).toBe('border-bottom-right-radius'); + expect(declaration[20]).toBe('border-bottom-left-radius'); + expect(declaration[21]).toBe('font-size'); + expect(declaration[22]).toBe(undefined); }); }); @@ -100,7 +110,9 @@ describe('CSSStyleDeclaration', () => { declaration.borderRight = '1px dotted red'; - expect(declaration.border).toBe(''); + expect(element.getAttribute('style')).toBe( + 'border-width: 2px 1px 2px 2px; border-style: solid dotted solid solid; border-color: green red green green; border-image: initial;' + ); declaration.borderRight = '2px solid green'; @@ -120,7 +132,7 @@ describe('CSSStyleDeclaration', () => { element.setAttribute('style', `border: 2px solid green;border-radius: 2px;font-size: 12px;`); - expect(declaration.length).toBe(17); + expect(declaration.length).toBe(22); }); it('Returns length without element.', () => { @@ -130,7 +142,7 @@ describe('CSSStyleDeclaration', () => { declaration.borderRadius = '2px'; declaration.fontSize = '12px'; - expect(declaration.length).toBe(17); + expect(declaration.length).toBe(22); }); }); @@ -209,12 +221,17 @@ describe('CSSStyleDeclaration', () => { expect(declaration.item(9)).toBe('border-right-color'); expect(declaration.item(10)).toBe('border-bottom-color'); expect(declaration.item(11)).toBe('border-left-color'); - expect(declaration.item(12)).toBe('border-top-left-radius'); - expect(declaration.item(13)).toBe('border-top-right-radius'); - expect(declaration.item(14)).toBe('border-bottom-right-radius'); - expect(declaration.item(15)).toBe('border-bottom-left-radius'); - expect(declaration.item(16)).toBe('font-size'); - expect(declaration.item(17)).toBe(''); + expect(declaration.item(12)).toBe('border-image-source'); + expect(declaration.item(13)).toBe('border-image-slice'); + expect(declaration.item(14)).toBe('border-image-width'); + expect(declaration.item(15)).toBe('border-image-outset'); + expect(declaration.item(16)).toBe('border-image-repeat'); + expect(declaration.item(17)).toBe('border-top-left-radius'); + expect(declaration.item(18)).toBe('border-top-right-radius'); + expect(declaration.item(19)).toBe('border-bottom-right-radius'); + expect(declaration.item(20)).toBe('border-bottom-left-radius'); + expect(declaration.item(21)).toBe('font-size'); + expect(declaration.item(22)).toBe(''); }); it('Returns an item by index without element.', () => { @@ -234,12 +251,17 @@ describe('CSSStyleDeclaration', () => { expect(declaration.item(9)).toBe('border-right-color'); expect(declaration.item(10)).toBe('border-bottom-color'); expect(declaration.item(11)).toBe('border-left-color'); - expect(declaration.item(12)).toBe('border-top-left-radius'); - expect(declaration.item(13)).toBe('border-top-right-radius'); - expect(declaration.item(14)).toBe('border-bottom-right-radius'); - expect(declaration.item(15)).toBe('border-bottom-left-radius'); - expect(declaration.item(16)).toBe('font-size'); - expect(declaration.item(17)).toBe(''); + expect(declaration.item(12)).toBe('border-image-source'); + expect(declaration.item(13)).toBe('border-image-slice'); + expect(declaration.item(14)).toBe('border-image-width'); + expect(declaration.item(15)).toBe('border-image-outset'); + expect(declaration.item(16)).toBe('border-image-repeat'); + expect(declaration.item(17)).toBe('border-top-left-radius'); + expect(declaration.item(18)).toBe('border-top-right-radius'); + expect(declaration.item(19)).toBe('border-bottom-right-radius'); + expect(declaration.item(20)).toBe('border-bottom-left-radius'); + expect(declaration.item(21)).toBe('font-size'); + expect(declaration.item(22)).toBe(''); }); }); @@ -325,7 +347,6 @@ describe('CSSStyleDeclaration', () => { it('Returns a CSS property without element.', () => { const declaration = new CSSStyleDeclaration(); - debugger; declaration.cssText = `border: 2px solid green;border-radius: 2px;font-size: 12px;`; expect(declaration.getPropertyValue('border')).toBe('2px solid green'); diff --git a/packages/happy-dom/test/window/Window.test.ts b/packages/happy-dom/test/window/Window.test.ts index 7b048abb7..73d701249 100644 --- a/packages/happy-dom/test/window/Window.test.ts +++ b/packages/happy-dom/test/window/Window.test.ts @@ -234,7 +234,7 @@ describe('Window', () => { document.body.appendChild(documentStyle); document.body.appendChild(parent); - expect(computedStyle.font).toBe('12px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif'); + expect(computedStyle.font).toBe('12px / 1.5 "Helvetica Neue", Helvetica, Arial, sans-serif'); }); }); From 8aff1f9621ad97099640f4642493f57811282f7f Mon Sep 17 00:00:00 2001 From: David Ortner Date: Thu, 8 Sep 2022 17:36:23 +0200 Subject: [PATCH 37/84] #344@trivial: Continue on CSSStyleDeclaration. --- .../CSSStyleDeclarationPropertySetParser.ts | 211 +++++++++------ .../CSSStyleDeclarationValueParser.ts | 14 + .../declaration/CSSStyleDeclaration.test.ts | 244 +++++++++++++++++- 3 files changed, 395 insertions(+), 74 deletions(-) diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts index 0d002c1e7..1c9a2d5be 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts @@ -519,8 +519,19 @@ export default class CSSStyleDeclarationPropertySetParser { }; } - const parsedValue = value.replace(/[ ]*\/[ ]*/g, '/'); + let parsedValue = value.replace(/[ ]*\/[ ]*/g, '/'); + const sourceMatch = parsedValue.match(/ *([a-zA-Z-]+\([^)]*\)) */); + + if (sourceMatch) { + parsedValue = parsedValue.replace(sourceMatch[0], ''); + } + const parts = parsedValue.split(/ +/); + + if (sourceMatch) { + parts.push(sourceMatch[1]); + } + const properties = { ...this.getBorderImageSource('none', important), ...this.getBorderImageSlice('100%', important), @@ -529,30 +540,35 @@ export default class CSSStyleDeclarationPropertySetParser { ...this.getBorderImageRepeat('stretch', important) }; - for (const part of parts) { - if (part.includes('/')) { + for (let i = 0, max = parts.length; i < max; i++) { + const part = parts[i]; + const previousPart = i > 0 ? parts[i - 1] : ''; + + if (!part.startsWith('url') && part.includes('/')) { const [slice, width, outset] = part.split('/'); - const borderImageSlice = this.getBorderImageSlice(slice, important); + const borderImageSlice = + this.getBorderImageSlice(`${previousPart} ${slice}`, important) || + this.getBorderImageSlice(slice, important); const borderImageWidth = this.getBorderImageWidth(width, important); - const borderImageOutset = this.getBorderImageOutset(outset, important); + const borderImageOutset = outset && this.getBorderImageOutset(outset, important); - if (borderImageSlice === null || borderImageWidth === null || borderImageOutset === null) { + if (!borderImageSlice || !borderImageWidth || borderImageOutset === null) { return null; } - Object.assign(properties, borderImageSlice); - Object.assign(properties, borderImageWidth); - Object.assign(properties, borderImageOutset); + Object.assign(properties, borderImageSlice, borderImageWidth, borderImageOutset); } else { + const slice = + this.getBorderImageSlice(`${previousPart} ${part}`, important) || + this.getBorderImageSlice(part, important); const source = this.getBorderImageSource(part, important); const repeat = this.getBorderImageRepeat(part, important); - if (source === null && repeat === null) { + if (!slice && !source && !repeat) { return null; } - Object.assign(properties, source); - Object.assign(properties, repeat); + Object.assign(properties, slice, source, repeat); } } @@ -583,12 +599,18 @@ export default class CSSStyleDeclarationPropertySetParser { }; } + const parsedValue = + CSSStyleDeclarationValueParser.getURL(value) || + CSSStyleDeclarationValueParser.getGradient(value); + + if (!parsedValue) { + return null; + } + return { 'border-image-source': { important, - value: - CSSStyleDeclarationValueParser.getURL(value) || - CSSStyleDeclarationValueParser.getGradient(value) + value: parsedValue } }; } @@ -617,22 +639,45 @@ export default class CSSStyleDeclarationPropertySetParser { }; } - const parts = lowerValue.split(/ +/); + if (lowerValue !== lowerValue.trim()) { + return null; + } + + const regexp = /(fill)|(calc\([^^)]+\))|([0-9]+%)|([0-9]+)/g; + const values = []; + let match; + + while ((match = regexp.exec(lowerValue))) { + const previousCharacter = lowerValue[match.index - 1]; + const nextCharacter = lowerValue[match.index + match[0].length]; - for (const part of parts) { if ( - part !== 'fill' && - !CSSStyleDeclarationValueParser.getPercentage(part) && - !CSSStyleDeclarationValueParser.getInteger(part) + (previousCharacter && previousCharacter !== ' ') || + (nextCharacter && nextCharacter !== ' ') ) { return null; } + + const fill = match[1] && 'fill'; + const calc = match[2] && CSSStyleDeclarationValueParser.getCalc(match[2]); + const percentage = match[3] && CSSStyleDeclarationValueParser.getPercentage(match[3]); + const integer = match[4] && CSSStyleDeclarationValueParser.getInteger(match[4]); + + if (!fill && !calc && !percentage && !integer) { + return null; + } + + values.push(fill || calc || percentage || integer); + } + + if (!values.length || values.length > 4) { + return null; } return { 'border-image-slice': { important, - value: lowerValue + value: values.join(' ') } }; } @@ -1176,26 +1221,31 @@ export default class CSSStyleDeclarationPropertySetParser { return { ...this.getBorderTopWidth(globalValue, important), ...this.getBorderTopStyle(globalValue, important), - ...this.getBorderTopColor(globalValue, important), - ...this.getBorderImage(globalValue, important) + ...this.getBorderTopColor(globalValue, important) }; } + const properties = { + ...this.getBorderTopWidth('initial', important), + ...this.getBorderTopStyle('initial', important), + ...this.getBorderTopColor('initial', important) + }; + const parts = value.split(/ +/); - const borderWidth = parts[0] ? this.getBorderTopWidth(parts[0], important) : ''; - const borderStyle = parts[1] ? this.getBorderTopStyle(parts[1], important) : ''; - const borderColor = parts[2] ? this.getBorderTopColor(parts[2], important) : ''; - if (borderWidth && borderStyle !== null && borderColor !== null) { - return { - ...this.getBorderImage('initial', important), - ...borderWidth, - ...borderStyle, - ...borderColor - }; + for (const part of parts) { + const width = this.getBorderTopWidth(part, important); + const style = this.getBorderTopStyle(part, important); + const color = this.getBorderTopColor(part, important); + + if (width === null && style === null && color === null) { + return null; + } + + Object.assign(properties, width, style, color); } - return null; + return properties; } /** @@ -1215,26 +1265,31 @@ export default class CSSStyleDeclarationPropertySetParser { return { ...this.getBorderRightWidth(globalValue, important), ...this.getBorderRightStyle(globalValue, important), - ...this.getBorderRightColor(globalValue, important), - ...this.getBorderImage(globalValue, important) + ...this.getBorderRightColor(globalValue, important) }; } + const properties = { + ...this.getBorderRightWidth('initial', important), + ...this.getBorderRightStyle('initial', important), + ...this.getBorderRightColor('initial', important) + }; + const parts = value.split(/ +/); - const borderWidth = parts[0] ? this.getBorderRightWidth(parts[0], important) : ''; - const borderStyle = parts[1] ? this.getBorderRightStyle(parts[1], important) : ''; - const borderColor = parts[2] ? this.getBorderRightColor(parts[2], important) : ''; - if (borderWidth && borderStyle !== null && borderColor !== null) { - return { - ...this.getBorderImage('initial', important), - ...borderWidth, - ...borderStyle, - ...borderColor - }; + for (const part of parts) { + const width = this.getBorderRightWidth(part, important); + const style = this.getBorderRightStyle(part, important); + const color = this.getBorderRightColor(part, important); + + if (width === null && style === null && color === null) { + return null; + } + + Object.assign(properties, width, style, color); } - return null; + return properties; } /** @@ -1254,26 +1309,31 @@ export default class CSSStyleDeclarationPropertySetParser { return { ...this.getBorderBottomWidth(globalValue, important), ...this.getBorderBottomStyle(globalValue, important), - ...this.getBorderBottomColor(globalValue, important), - ...this.getBorderImage(globalValue, important) + ...this.getBorderBottomColor(globalValue, important) }; } + const properties = { + ...this.getBorderBottomWidth('initial', important), + ...this.getBorderBottomStyle('initial', important), + ...this.getBorderBottomColor('initial', important) + }; + const parts = value.split(/ +/); - const borderWidth = parts[0] ? this.getBorderBottomWidth(parts[0], important) : ''; - const borderStyle = parts[1] ? this.getBorderBottomStyle(parts[1], important) : ''; - const borderColor = parts[2] ? this.getBorderBottomColor(parts[2], important) : ''; - if (borderWidth && borderStyle !== null && borderColor !== null) { - return { - ...this.getBorderImage('initial', important), - ...borderWidth, - ...borderStyle, - ...borderColor - }; + for (const part of parts) { + const width = this.getBorderBottomWidth(part, important); + const style = this.getBorderBottomStyle(part, important); + const color = this.getBorderBottomColor(part, important); + + if (width === null && style === null && color === null) { + return null; + } + + Object.assign(properties, width, style, color); } - return null; + return properties; } /** @@ -1293,26 +1353,31 @@ export default class CSSStyleDeclarationPropertySetParser { return { ...this.getBorderLeftWidth(globalValue, important), ...this.getBorderLeftStyle(globalValue, important), - ...this.getBorderLeftColor(globalValue, important), - ...this.getBorderImage(globalValue, important) + ...this.getBorderLeftColor(globalValue, important) }; } + const properties = { + ...this.getBorderLeftWidth('initial', important), + ...this.getBorderLeftStyle('initial', important), + ...this.getBorderLeftColor('initial', important) + }; + const parts = value.split(/ +/); - const borderWidth = parts[0] ? this.getBorderLeftWidth(parts[0], important) : ''; - const borderStyle = parts[1] ? this.getBorderLeftStyle(parts[1], important) : ''; - const borderColor = parts[2] ? this.getBorderLeftColor(parts[2], important) : ''; - if (borderWidth && borderStyle !== null && borderColor !== null) { - return { - ...this.getBorderImage('initial', important), - ...borderWidth, - ...borderStyle, - ...borderColor - }; + for (const part of parts) { + const width = this.getBorderLeftWidth(part, important); + const style = this.getBorderLeftStyle(part, important); + const color = this.getBorderLeftColor(part, important); + + if (width === null && style === null && color === null) { + return null; + } + + Object.assign(properties, width, style, color); } - return null; + return properties; } /** diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts index 6d5bb7eca..3f77e2128 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts @@ -7,6 +7,7 @@ const DEGREE_REGEXP = /^[0-9]+deg$/; const URL_REGEXP = /^url\(\s*([^)]*)\s*\)$/; const INTEGER_REGEXP = /^[0-9]+$/; const FLOAT_REGEXP = /^[0-9.]+$/; +const CALC_REGEXP = /^calc\([^^)]+\)$/; const GRADIENT_REGEXP = /^(repeating-linear|linear|radial|repeating-radial|conic|repeating-conic)-gradient\([^)]+\)$/; const GLOBALS = ['inherit', 'initial', 'unset', 'revert']; @@ -209,6 +210,19 @@ export default class CSSStyleDeclarationValueParser { return null; } + /** + * Returns calc. + * + * @param value Value. + * @returns Parsed value. + */ + public static getCalc(value: string): string { + if (CALC_REGEXP.test(value)) { + return value; + } + return null; + } + /** * Returns measurement. * diff --git a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts index ea6756d88..f96bdb613 100644 --- a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts +++ b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts @@ -80,7 +80,7 @@ describe('CSSStyleDeclaration', () => { }); describe('get border()', () => { - it('Returns style property on element.', () => { + it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); element.setAttribute('style', 'border: 2px solid green'); @@ -108,6 +108,13 @@ describe('CSSStyleDeclaration', () => { expect(declaration.borderLeftWidth).toBe('2px'); expect(declaration.borderLeftStyle).toBe('solid'); + expect(declaration.borderImage).toBe('initial'); + expect(declaration.borderImageOutset).toBe('initial'); + expect(declaration.borderImageRepeat).toBe('initial'); + expect(declaration.borderImageSlice).toBe('initial'); + expect(declaration.borderImageSource).toBe('initial'); + expect(declaration.borderImageWidth).toBe('initial'); + declaration.borderRight = '1px dotted red'; expect(element.getAttribute('style')).toBe( @@ -123,6 +130,241 @@ describe('CSSStyleDeclaration', () => { declaration.borderWidth = '1px'; expect(declaration.border).toBe('1px dotted red'); + + element.setAttribute('style', 'border: green solid'); + + expect(declaration.border).toBe('solid green'); + }); + }); + + describe('get borderTop()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-top: green 2px solid'); + + expect(declaration.border).toBe(''); + + expect(declaration.borderTop).toBe('2px solid green'); + expect(declaration.borderRight).toBe(''); + expect(declaration.borderBottom).toBe(''); + expect(declaration.borderLeft).toBe(''); + expect(declaration.borderTopColor).toBe('green'); + expect(declaration.borderTopWidth).toBe('2px'); + expect(declaration.borderTopStyle).toBe('solid'); + expect(declaration.borderImage).toBe(''); + }); + }); + + describe('get borderRight()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-right: green solid 2px'); + + expect(declaration.border).toBe(''); + + expect(declaration.borderTop).toBe(''); + expect(declaration.borderRight).toBe('2px solid green'); + expect(declaration.borderBottom).toBe(''); + expect(declaration.borderLeft).toBe(''); + expect(declaration.borderRightColor).toBe('green'); + expect(declaration.borderRightWidth).toBe('2px'); + expect(declaration.borderRightStyle).toBe('solid'); + expect(declaration.borderImage).toBe(''); + }); + }); + + describe('get borderBottom()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-bottom: green solid 2px'); + + expect(declaration.border).toBe(''); + + expect(declaration.borderTop).toBe(''); + expect(declaration.borderRight).toBe(''); + expect(declaration.borderBottom).toBe('2px solid green'); + expect(declaration.borderLeft).toBe(''); + expect(declaration.borderBottomColor).toBe('green'); + expect(declaration.borderBottomWidth).toBe('2px'); + expect(declaration.borderBottomStyle).toBe('solid'); + expect(declaration.borderImage).toBe(''); + }); + }); + + describe('get borderLeft()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-left: green solid 2px'); + + expect(declaration.border).toBe(''); + + expect(declaration.borderTop).toBe(''); + expect(declaration.borderRight).toBe(''); + expect(declaration.borderBottom).toBe(''); + expect(declaration.borderLeft).toBe('2px solid green'); + expect(declaration.borderLeftColor).toBe('green'); + expect(declaration.borderLeftWidth).toBe('2px'); + expect(declaration.borderLeftStyle).toBe('solid'); + expect(declaration.borderImage).toBe(''); + }); + }); + + describe('get borderWidth()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-width: 1px 2px 3px 4px'); + + expect(declaration.borderTopWidth).toBe('1px'); + expect(declaration.borderRightWidth).toBe('2px'); + expect(declaration.borderBottomWidth).toBe('3px'); + expect(declaration.borderLeftWidth).toBe('4px'); + + element.setAttribute('style', 'border-width: 2px'); + + expect(declaration.borderTopWidth).toBe('2px'); + expect(declaration.borderRightWidth).toBe('2px'); + expect(declaration.borderBottomWidth).toBe('2px'); + expect(declaration.borderLeftWidth).toBe('2px'); + }); + }); + + describe('get borderStyle()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-style: none hidden dotted dashed'); + + expect(declaration.borderTopStyle).toBe('none'); + expect(declaration.borderRightStyle).toBe('hidden'); + expect(declaration.borderBottomStyle).toBe('dotted'); + expect(declaration.borderLeftStyle).toBe('dashed'); + + element.setAttribute('style', 'border-style: hidden'); + + expect(declaration.borderTopStyle).toBe('hidden'); + expect(declaration.borderRightStyle).toBe('hidden'); + expect(declaration.borderBottomStyle).toBe('hidden'); + expect(declaration.borderLeftStyle).toBe('hidden'); + }); + }); + + describe('get borderColor()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-color: #000 #ffffff rgba(135,200,150,0.5) blue'); + + expect(declaration.borderTopColor).toBe('#000'); + expect(declaration.borderRightColor).toBe('#ffffff'); + expect(declaration.borderBottomColor).toBe('rgba(135,200,150,0.5)'); + expect(declaration.borderLeftColor).toBe('blue'); + + element.setAttribute('style', 'border-color: rgb(135,200,150)'); + + expect(declaration.borderTopColor).toBe('rgb(135,200,150)'); + expect(declaration.borderRightColor).toBe('rgb(135,200,150)'); + expect(declaration.borderBottomColor).toBe('rgb(135,200,150)'); + expect(declaration.borderLeftColor).toBe('rgb(135,200,150)'); + }); + }); + + describe('get borderImage()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute( + 'style', + 'border-image: repeating-linear-gradient(30deg, #4d9f0c, #9198e5, #4d9f0c 20px) 60' + ); + + expect(declaration.borderImage).toBe( + 'repeating-linear-gradient(30deg, #4d9f0c, #9198e5, #4d9f0c 20px) 60 / 1 / 0 stretch' + ); + + element.setAttribute('style', `border-image: url('/media/examples/border-diamonds.png') 30`); + + expect(declaration.borderImage).toBe( + `url('/media/examples/border-diamonds.png') 30 / 1 / 0 stretch` + ); + + element.setAttribute( + 'style', + `border-image: url('/media/examples/border-diamonds.png') 30 / 19px round` + ); + + expect(declaration.borderImage).toBe( + `url('/media/examples/border-diamonds.png') 30 / 19px / 0 round` + ); + + element.setAttribute( + 'style', + `border-image: url('/media/examples/border-diamonds.png') 10 fill / 20px / 30px space` + ); + + expect(declaration.borderImage).toBe( + `url('/media/examples/border-diamonds.png') 10 fill / 20px / 30px space` + ); + expect(declaration.borderImageOutset).toBe('30px'); + expect(declaration.borderImageRepeat).toBe('space'); + expect(declaration.borderImageSlice).toBe('10 fill'); + expect(declaration.borderImageWidth).toBe('20px'); + + element.setAttribute('style', `border-image: linear-gradient(#f6b73c, #4d9f0c) 30;`); + + expect(declaration.borderImage).toBe(`linear-gradient(#f6b73c, #4d9f0c) 30 / 1 / 0 stretch`); + }); + }); + + describe('get borderImageSource()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute( + 'style', + `border-image-source: url('/media/examples/border-diamonds.png')` + ); + + expect(declaration.borderImageSource).toBe(`url('/media/examples/border-diamonds.png')`); + + element.setAttribute('style', `border-image-source: NONE`); + + expect(declaration.borderImageSource).toBe(`none`); + + element.setAttribute( + 'style', + `border-image-source: repeating-linear-gradient(30deg, #4d9f0c, #9198e5, #4d9f0c 20px)` + ); + + expect(declaration.borderImageSource).toBe( + `repeating-linear-gradient(30deg, #4d9f0c, #9198e5, #4d9f0c 20px)` + ); + }); + }); + + describe('get borderImageSlice()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-image-slice: 30'); + + expect(declaration.borderImageSlice).toBe('30'); + + element.setAttribute('style', 'border-image-slice: 30 fill'); + + debugger; + expect(declaration.borderImageSlice).toBe('30 fill'); + + element.setAttribute( + 'style', + 'border-image-slice: calc(50 / 184 * 100%) calc(80 / 284 * 100%) fill' + ); + + expect(declaration.borderImageSlice).toBe('calc(50 / 184 * 100%) calc(80 / 284 * 100%) fill'); }); }); From b0d14bd446e1fa98c5025ed2131cb630a7e133b4 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Fri, 9 Sep 2022 09:29:32 +0200 Subject: [PATCH 38/84] #344@trivial: Continue on CSSStyleDeclaration. --- .../CSSStyleDeclarationPropertySetParser.ts | 10 +- .../declaration/CSSStyleDeclaration.test.ts | 223 +++++++++++++++++- 2 files changed, 231 insertions(+), 2 deletions(-) diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts index 1c9a2d5be..50a16d156 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts @@ -708,6 +708,10 @@ export default class CSSStyleDeclarationPropertySetParser { const parts = lowerValue.split(/ +/); + if (parts.length > 4) { + return null; + } + for (const part of parts) { if ( !CSSStyleDeclarationValueParser.getInteger(part) && @@ -751,10 +755,14 @@ export default class CSSStyleDeclarationPropertySetParser { const parts = value.split(/ +/); + if (parts.length > 4) { + return null; + } + for (const part of parts) { if ( !CSSStyleDeclarationValueParser.getLength(part) && - !CSSStyleDeclarationValueParser.getFloat(value) + !CSSStyleDeclarationValueParser.getFloat(part) ) { return null; } diff --git a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts index f96bdb613..db6777aee 100644 --- a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts +++ b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts @@ -356,7 +356,6 @@ describe('CSSStyleDeclaration', () => { element.setAttribute('style', 'border-image-slice: 30 fill'); - debugger; expect(declaration.borderImageSlice).toBe('30 fill'); element.setAttribute( @@ -368,6 +367,228 @@ describe('CSSStyleDeclaration', () => { }); }); + describe('get borderImageWidth()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-image-width: auto'); + + expect(declaration.borderImageWidth).toBe('auto'); + + element.setAttribute('style', 'border-image-width: 25%'); + + expect(declaration.borderImageWidth).toBe('25%'); + + element.setAttribute('style', 'border-image-width: 5% 2em 10% auto'); + + expect(declaration.borderImageWidth).toBe('5% 2em 10% auto'); + }); + }); + + describe('get borderImageOutset()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-image-outset: 1rem'); + + expect(declaration.borderImageOutset).toBe('1rem'); + + element.setAttribute('style', 'border-image-outset: 1 1.2'); + + expect(declaration.borderImageOutset).toBe('1 1.2'); + + element.setAttribute('style', 'border-image-outset: 7px 12em 14cm 5px'); + + expect(declaration.borderImageOutset).toBe('7px 12em 14cm 5px'); + }); + }); + + describe('get borderImageRepeat()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-image-repeat: stretch'); + + expect(declaration.borderImageRepeat).toBe('stretch'); + + element.setAttribute('style', 'border-image-repeat: repeat'); + + expect(declaration.borderImageRepeat).toBe('repeat'); + + element.setAttribute('style', 'border-image-repeat: round stretch'); + + expect(declaration.borderImageRepeat).toBe('round stretch'); + }); + }); + + describe('get borderTopWidth()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-top-width: thick'); + + expect(declaration.borderTopWidth).toBe('thick'); + + element.setAttribute('style', 'border-top-width: 2em'); + + expect(declaration.borderTopWidth).toBe('2em'); + }); + }); + + describe('get borderRightWidth()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-right-width: thick'); + + expect(declaration.borderRightWidth).toBe('thick'); + + element.setAttribute('style', 'border-right-width: 2em'); + + expect(declaration.borderRightWidth).toBe('2em'); + }); + }); + + describe('get borderBottomWidth()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-bottom-width: thick'); + + expect(declaration.borderBottomWidth).toBe('thick'); + + element.setAttribute('style', 'border-bottom-width: 2em'); + + expect(declaration.borderBottomWidth).toBe('2em'); + }); + }); + + describe('get borderLeftWidth()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-left-width: thick'); + + expect(declaration.borderLeftWidth).toBe('thick'); + + element.setAttribute('style', 'border-left-width: 2em'); + + expect(declaration.borderLeftWidth).toBe('2em'); + }); + }); + + describe('get borderTopColor()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-top-color: red'); + + expect(declaration.borderTopColor).toBe('red'); + + element.setAttribute('style', 'border-top-color: rgba(100, 100, 100, 0.5)'); + + expect(declaration.borderTopColor).toBe('rgba(100, 100, 100, 0.5)'); + }); + }); + + describe('get borderRightColor()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-right-color: red'); + + expect(declaration.borderRightColor).toBe('red'); + + element.setAttribute('style', 'border-right-color: rgba(100, 100, 100, 0.5)'); + + expect(declaration.borderRightColor).toBe('rgba(100, 100, 100, 0.5)'); + }); + }); + + describe('get borderBottomColor()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-bottom-color: red'); + + expect(declaration.borderBottomColor).toBe('red'); + + element.setAttribute('style', 'border-bottom-color: rgba(100, 100, 100, 0.5)'); + + expect(declaration.borderBottomColor).toBe('rgba(100, 100, 100, 0.5)'); + }); + }); + + describe('get borderLeftColor()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-left-color: red'); + + expect(declaration.borderLeftColor).toBe('red'); + + element.setAttribute('style', 'border-left-color: rgba(100, 100, 100, 0.5)'); + + expect(declaration.borderLeftColor).toBe('rgba(100, 100, 100, 0.5)'); + }); + }); + + describe('get borderTopStyle()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-top-style: dotted'); + + expect(declaration.borderTopStyle).toBe('dotted'); + + element.setAttribute('style', 'border-top-style: solid'); + + expect(declaration.borderTopStyle).toBe('solid'); + }); + }); + + describe('get borderRightStyle()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-right-style: dotted'); + + expect(declaration.borderRightStyle).toBe('dotted'); + + element.setAttribute('style', 'border-right-style: solid'); + + expect(declaration.borderRightStyle).toBe('solid'); + }); + }); + + describe('get borderBottomStyle()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-bottom-style: dotted'); + + expect(declaration.borderBottomStyle).toBe('dotted'); + + element.setAttribute('style', 'border-bottom-style: solid'); + + expect(declaration.borderBottomStyle).toBe('solid'); + }); + }); + + describe('get borderLeftStyle()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-left-style: dotted'); + + expect(declaration.borderLeftStyle).toBe('dotted'); + + element.setAttribute('style', 'border-left-style: solid'); + + expect(declaration.borderLeftStyle).toBe('solid'); + }); + }); + describe('get length()', () => { it('Returns length when of styles on element.', () => { const declaration = new CSSStyleDeclaration(element); From 4ebf7b72bec6ead5a13455f5dee08cba133e7595 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Fri, 9 Sep 2022 16:21:45 +0200 Subject: [PATCH 39/84] #344@trivial: Continues on CSSStyleDeclaration. --- .../CSSStyleDeclarationPropertyManager.ts | 11 +- .../CSSStyleDeclarationPropertySetParser.ts | 64 +-- .../CSSStyleDeclarationValueParser.ts | 28 +- .../declaration/CSSStyleDeclaration.test.ts | 420 ++++++++++++++++++ 4 files changed, 486 insertions(+), 37 deletions(-) diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts index 04f54e6f2..ea645febe 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts @@ -242,6 +242,11 @@ export default class CSSStyleDeclarationPropertyManager { * @param important Important. */ public set(name: string, value: string, important: boolean): void { + if (value === null) { + this.remove(name); + return; + } + let properties = null; switch (name) { @@ -356,15 +361,15 @@ export default class CSSStyleDeclarationPropertyManager { case 'css-float': properties = CSSStyleDeclarationPropertySetParser.getCSSFloat(value, important); break; + case 'float': + properties = CSSStyleDeclarationPropertySetParser.getFloat(value, important); + break; case 'display': properties = CSSStyleDeclarationPropertySetParser.getDisplay(value, important); break; case 'direction': properties = CSSStyleDeclarationPropertySetParser.getDirection(value, important); break; - case 'float': - properties = CSSStyleDeclarationPropertySetParser.getFloat(value, important); - break; case 'flex': properties = CSSStyleDeclarationPropertySetParser.getFlex(value, important); break; diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts index 50a16d156..5d140c1a8 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts @@ -22,7 +22,7 @@ const BACKGROUND_CLIP = ['border-box', 'padding-box', 'content-box']; const BACKGROUND_ATTACHMENT = ['scroll', 'fixed']; const FLEX_BASIS = ['auto', 'fill', 'max-content', 'min-content', 'fit-content', 'content']; const CLEAR = ['none', 'left', 'right', 'both']; -const FLOAT = ['none', 'left', 'right']; +const FLOAT = ['none', 'left', 'right', 'inline-start', 'inline-end']; 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']; @@ -168,7 +168,7 @@ export default class CSSStyleDeclarationPropertySetParser { } { const parsedValue = CSSStyleDeclarationValueParser.getGlobal(value) || - CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + CSSStyleDeclarationValueParser.getContentMeasurement(value); return parsedValue ? { top: { value: parsedValue, important } } : null; } @@ -187,7 +187,7 @@ export default class CSSStyleDeclarationPropertySetParser { } { const parsedValue = CSSStyleDeclarationValueParser.getGlobal(value) || - CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + CSSStyleDeclarationValueParser.getContentMeasurement(value); return parsedValue ? { right: { value: parsedValue, important } } : null; } @@ -206,7 +206,7 @@ export default class CSSStyleDeclarationPropertySetParser { } { const parsedValue = CSSStyleDeclarationValueParser.getGlobal(value) || - CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + CSSStyleDeclarationValueParser.getContentMeasurement(value); return parsedValue ? { bottom: { value: parsedValue, important } } : null; } @@ -225,7 +225,7 @@ export default class CSSStyleDeclarationPropertySetParser { } { const parsedValue = CSSStyleDeclarationValueParser.getGlobal(value) || - CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + CSSStyleDeclarationValueParser.getContentMeasurement(value); return parsedValue ? { left: { value: parsedValue, important } } : null; } @@ -318,8 +318,7 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { - const float = - CSSStyleDeclarationValueParser.getGlobal(value) || this.getFloat(value, important); + const float = this.getFloat(value, important); return float ? { 'css-float': float['float'] } : null; } @@ -715,7 +714,7 @@ export default class CSSStyleDeclarationPropertySetParser { for (const part of parts) { if ( !CSSStyleDeclarationValueParser.getInteger(part) && - !CSSStyleDeclarationValueParser.getMeasurementOrAuto(part) + !CSSStyleDeclarationValueParser.getContentMeasurement(part) ) { return null; } @@ -1121,8 +1120,8 @@ export default class CSSStyleDeclarationPropertySetParser { return { ...this.getBorderTopLeftRadius(globalValue, important), ...this.getBorderTopRightRadius(globalValue, important), - ...this.getBorderTopRightRadius(globalValue, important), - ...this.getBorderTopRightRadius(globalValue, important) + ...this.getBorderBottomRightRadius(globalValue, important), + ...this.getBorderBottomLeftRadius(globalValue, important) }; } @@ -1548,7 +1547,7 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const margin = CSSStyleDeclarationValueParser.getGlobal(value) || - CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + CSSStyleDeclarationValueParser.getContentMeasurement(value); return margin ? { 'margin-top': { value: margin, important } } : null; } @@ -1565,7 +1564,7 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const margin = CSSStyleDeclarationValueParser.getGlobal(value) || - CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + CSSStyleDeclarationValueParser.getContentMeasurement(value); return margin ? { 'margin-right': { value: margin, important } } : null; } @@ -1582,7 +1581,7 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const margin = CSSStyleDeclarationValueParser.getGlobal(value) || - CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + CSSStyleDeclarationValueParser.getContentMeasurement(value); return margin ? { 'margin-bottom': { value: margin, important } } : null; } @@ -1599,7 +1598,7 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const margin = CSSStyleDeclarationValueParser.getGlobal(value) || - CSSStyleDeclarationValueParser.getMeasurementOrAuto(value); + CSSStyleDeclarationValueParser.getContentMeasurement(value); return margin ? { 'margin-left': { value: margin, important } } : null; } @@ -1640,20 +1639,30 @@ export default class CSSStyleDeclarationPropertySetParser { }; } - const parts = value.split(/ +/); - const flexGrow = parts[0] && this.getFlexGrow(parts[0], important); - const flexShrink = parts[1] && this.getFlexShrink(parts[1], important); - const flexBasis = parts[2] && this.getFlexBasis(parts[2], important); + const measurement = CSSStyleDeclarationValueParser.getContentMeasurement(lowerValue); - if (flexGrow && flexShrink && flexBasis) { + if (measurement) { return { - ...flexBasis, - ...flexGrow, - ...flexBasis + ...this.getFlexGrow('1', important), + ...this.getFlexShrink('1', important), + ...this.getFlexBasis(measurement, important) }; } - return null; + const parts = value.split(/ +/); + const flexGrow = this.getFlexGrow(parts[0], important); + const flexShrink = this.getFlexShrink(parts[1] || '1', important); + const flexBasis = this.getFlexBasis(parts[2] || '0%', important); + + if (!flexGrow || !flexShrink || !flexBasis) { + return null; + } + + return { + ...flexGrow, + ...flexShrink, + ...flexBasis + }; } /** @@ -1673,7 +1682,8 @@ export default class CSSStyleDeclarationPropertySetParser { if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || FLEX_BASIS.includes(lowerValue)) { return { 'flex-basis': { value: lowerValue, important } }; } - return { 'flex-basis': { value: CSSStyleDeclarationValueParser.getLength(value), important } }; + const measurement = CSSStyleDeclarationValueParser.getContentMeasurement(value); + return measurement ? { 'flex-basis': { value: measurement, important } } : null; } /** @@ -1828,15 +1838,15 @@ export default class CSSStyleDeclarationPropertySetParser { if ( parts[0] !== 'cover' && parts[0] !== 'contain' && - !CSSStyleDeclarationValueParser.getMeasurementOrAuto(parts[0]) + !CSSStyleDeclarationValueParser.getContentMeasurement(parts[0]) ) { return null; } parsed.push(parts[0]); } else { if ( - !CSSStyleDeclarationValueParser.getMeasurementOrAuto(parts[0]) || - !CSSStyleDeclarationValueParser.getMeasurementOrAuto(parts[1]) + !CSSStyleDeclarationValueParser.getContentMeasurement(parts[0]) || + !CSSStyleDeclarationValueParser.getContentMeasurement(parts[1]) ) { return null; } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts index 3f77e2128..746fd7bc0 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts @@ -8,6 +8,7 @@ const URL_REGEXP = /^url\(\s*([^)]*)\s*\)$/; const INTEGER_REGEXP = /^[0-9]+$/; const FLOAT_REGEXP = /^[0-9.]+$/; const CALC_REGEXP = /^calc\([^^)]+\)$/; +const FIT_CONTENT_REGEXP = /^fit-content\([^^)]+\)$/; const GRADIENT_REGEXP = /^(repeating-linear|linear|radial|repeating-radial|conic|repeating-conic)-gradient\([^)]+\)$/; const GLOBALS = ['inherit', 'initial', 'unset', 'revert']; @@ -223,6 +224,23 @@ export default class CSSStyleDeclarationValueParser { return null; } + /** + * Returns fit content. + * + * @param value Value. + * @returns Parsed value. + */ + public static getFitContent(value: string): string { + const lowerValue = value.toLowerCase(); + if (lowerValue === 'auto' || lowerValue === 'max-content' || lowerValue === 'min-content') { + return lowerValue; + } + if (FIT_CONTENT_REGEXP.test(lowerValue)) { + return lowerValue; + } + return null; + } + /** * Returns measurement. * @@ -234,17 +252,13 @@ export default class CSSStyleDeclarationValueParser { } /** - * Returns measurement or auto. + * Returns measurement or auto, min-content, max-content or fit-content. * * @param value Value. * @returns Parsed value. */ - public static getMeasurementOrAuto(value: string): string { - const lowerValue = value.toLowerCase(); - if (lowerValue === 'auto') { - return lowerValue; - } - return this.getMeasurement(value); + public static getContentMeasurement(value: string): string { + return this.getFitContent(value) || this.getMeasurement(value); } /** diff --git a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts index db6777aee..bf6f9113c 100644 --- a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts +++ b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts @@ -83,6 +83,32 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border: inherit'); + + expect(declaration.border).toBe('inherit'); + expect(declaration.borderTop).toBe('inherit'); + expect(declaration.borderRight).toBe('inherit'); + expect(declaration.borderBottom).toBe('inherit'); + expect(declaration.borderLeft).toBe('inherit'); + expect(declaration.borderTopColor).toBe('inherit'); + expect(declaration.borderRightColor).toBe('inherit'); + expect(declaration.borderBottomColor).toBe('inherit'); + expect(declaration.borderLeftColor).toBe('inherit'); + expect(declaration.borderTopWidth).toBe('inherit'); + expect(declaration.borderRightWidth).toBe('inherit'); + expect(declaration.borderBottomWidth).toBe('inherit'); + expect(declaration.borderLeftWidth).toBe('inherit'); + expect(declaration.borderTopStyle).toBe('inherit'); + expect(declaration.borderRightStyle).toBe('inherit'); + expect(declaration.borderBottomStyle).toBe('inherit'); + expect(declaration.borderLeftStyle).toBe('inherit'); + expect(declaration.borderImage).toBe('inherit'); + expect(declaration.borderImageOutset).toBe('inherit'); + expect(declaration.borderImageRepeat).toBe('inherit'); + expect(declaration.borderImageSlice).toBe('inherit'); + expect(declaration.borderImageSource).toBe('inherit'); + expect(declaration.borderImageWidth).toBe('inherit'); + element.setAttribute('style', 'border: 2px solid green'); expect(declaration.border).toBe('2px solid green'); @@ -141,6 +167,13 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-top: inherit'); + + expect(declaration.borderTop).toBe('inherit'); + expect(declaration.borderTopColor).toBe('inherit'); + expect(declaration.borderTopWidth).toBe('inherit'); + expect(declaration.borderTopStyle).toBe('inherit'); + element.setAttribute('style', 'border-top: green 2px solid'); expect(declaration.border).toBe(''); @@ -160,6 +193,13 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-right: inherit'); + + expect(declaration.borderRight).toBe('inherit'); + expect(declaration.borderRightColor).toBe('inherit'); + expect(declaration.borderRightWidth).toBe('inherit'); + expect(declaration.borderRightStyle).toBe('inherit'); + element.setAttribute('style', 'border-right: green solid 2px'); expect(declaration.border).toBe(''); @@ -179,6 +219,13 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-bottom: inherit'); + + expect(declaration.borderBottom).toBe('inherit'); + expect(declaration.borderBottomColor).toBe('inherit'); + expect(declaration.borderBottomWidth).toBe('inherit'); + expect(declaration.borderBottomStyle).toBe('inherit'); + element.setAttribute('style', 'border-bottom: green solid 2px'); expect(declaration.border).toBe(''); @@ -198,6 +245,13 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-left: inherit'); + + expect(declaration.borderLeft).toBe('inherit'); + expect(declaration.borderLeftColor).toBe('inherit'); + expect(declaration.borderLeftWidth).toBe('inherit'); + expect(declaration.borderLeftStyle).toBe('inherit'); + element.setAttribute('style', 'border-left: green solid 2px'); expect(declaration.border).toBe(''); @@ -217,6 +271,13 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-width: inherit'); + + expect(declaration.borderTopWidth).toBe('inherit'); + expect(declaration.borderRightWidth).toBe('inherit'); + expect(declaration.borderBottomWidth).toBe('inherit'); + expect(declaration.borderLeftWidth).toBe('inherit'); + element.setAttribute('style', 'border-width: 1px 2px 3px 4px'); expect(declaration.borderTopWidth).toBe('1px'); @@ -237,6 +298,13 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-style: inherit'); + + expect(declaration.borderTopStyle).toBe('inherit'); + expect(declaration.borderRightStyle).toBe('inherit'); + expect(declaration.borderBottomStyle).toBe('inherit'); + expect(declaration.borderLeftStyle).toBe('inherit'); + element.setAttribute('style', 'border-style: none hidden dotted dashed'); expect(declaration.borderTopStyle).toBe('none'); @@ -257,6 +325,13 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-color: inherit'); + + expect(declaration.borderTopColor).toBe('inherit'); + expect(declaration.borderRightColor).toBe('inherit'); + expect(declaration.borderBottomColor).toBe('inherit'); + expect(declaration.borderLeftColor).toBe('inherit'); + element.setAttribute('style', 'border-color: #000 #ffffff rgba(135,200,150,0.5) blue'); expect(declaration.borderTopColor).toBe('#000'); @@ -277,6 +352,14 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-image: inherit'); + expect(declaration.borderImage).toBe('inherit'); + expect(declaration.borderImageSource).toBe('inherit'); + expect(declaration.borderImageOutset).toBe('inherit'); + expect(declaration.borderImageRepeat).toBe('inherit'); + expect(declaration.borderImageSlice).toBe('inherit'); + expect(declaration.borderImageWidth).toBe('inherit'); + element.setAttribute( 'style', 'border-image: repeating-linear-gradient(30deg, #4d9f0c, #9198e5, #4d9f0c 20px) 60' @@ -309,6 +392,7 @@ describe('CSSStyleDeclaration', () => { expect(declaration.borderImage).toBe( `url('/media/examples/border-diamonds.png') 10 fill / 20px / 30px space` ); + expect(declaration.borderImageSource).toBe(`url('/media/examples/border-diamonds.png')`); expect(declaration.borderImageOutset).toBe('30px'); expect(declaration.borderImageRepeat).toBe('space'); expect(declaration.borderImageSlice).toBe('10 fill'); @@ -324,6 +408,10 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', `border-image-source: inherit`); + + expect(declaration.borderImageSource).toBe('inherit'); + element.setAttribute( 'style', `border-image-source: url('/media/examples/border-diamonds.png')` @@ -350,6 +438,10 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-image-slice: inherit'); + + expect(declaration.borderImageSlice).toBe('inherit'); + element.setAttribute('style', 'border-image-slice: 30'); expect(declaration.borderImageSlice).toBe('30'); @@ -371,6 +463,10 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-image-width: inherit'); + + expect(declaration.borderImageWidth).toBe('inherit'); + element.setAttribute('style', 'border-image-width: auto'); expect(declaration.borderImageWidth).toBe('auto'); @@ -389,6 +485,10 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-image-outset: inherit'); + + expect(declaration.borderImageOutset).toBe('inherit'); + element.setAttribute('style', 'border-image-outset: 1rem'); expect(declaration.borderImageOutset).toBe('1rem'); @@ -407,6 +507,10 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-image-repeat: inherit'); + + expect(declaration.borderImageRepeat).toBe('inherit'); + element.setAttribute('style', 'border-image-repeat: stretch'); expect(declaration.borderImageRepeat).toBe('stretch'); @@ -425,6 +529,10 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-top-width: inherit'); + + expect(declaration.borderTopWidth).toBe('inherit'); + element.setAttribute('style', 'border-top-width: thick'); expect(declaration.borderTopWidth).toBe('thick'); @@ -439,6 +547,10 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-right-width: inherit'); + + expect(declaration.borderRightWidth).toBe('inherit'); + element.setAttribute('style', 'border-right-width: thick'); expect(declaration.borderRightWidth).toBe('thick'); @@ -453,6 +565,10 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-bottom-width: inherit'); + + expect(declaration.borderBottomWidth).toBe('inherit'); + element.setAttribute('style', 'border-bottom-width: thick'); expect(declaration.borderBottomWidth).toBe('thick'); @@ -467,6 +583,10 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-left-width: inherit'); + + expect(declaration.borderLeftWidth).toBe('inherit'); + element.setAttribute('style', 'border-left-width: thick'); expect(declaration.borderLeftWidth).toBe('thick'); @@ -481,6 +601,10 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-top-color: inherit'); + + expect(declaration.borderTopColor).toBe('inherit'); + element.setAttribute('style', 'border-top-color: red'); expect(declaration.borderTopColor).toBe('red'); @@ -495,6 +619,10 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-right-color: inherit'); + + expect(declaration.borderRightColor).toBe('inherit'); + element.setAttribute('style', 'border-right-color: red'); expect(declaration.borderRightColor).toBe('red'); @@ -509,6 +637,10 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-bottom-color: inherit'); + + expect(declaration.borderBottomColor).toBe('inherit'); + element.setAttribute('style', 'border-bottom-color: red'); expect(declaration.borderBottomColor).toBe('red'); @@ -523,6 +655,10 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-left-color: inherit'); + + expect(declaration.borderLeftColor).toBe('inherit'); + element.setAttribute('style', 'border-left-color: red'); expect(declaration.borderLeftColor).toBe('red'); @@ -537,6 +673,10 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-top-style: inherit'); + + expect(declaration.borderTopStyle).toBe('inherit'); + element.setAttribute('style', 'border-top-style: dotted'); expect(declaration.borderTopStyle).toBe('dotted'); @@ -551,6 +691,10 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-right-style: inherit'); + + expect(declaration.borderRightStyle).toBe('inherit'); + element.setAttribute('style', 'border-right-style: dotted'); expect(declaration.borderRightStyle).toBe('dotted'); @@ -565,6 +709,10 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-bottom-style: inherit'); + + expect(declaration.borderBottomStyle).toBe('inherit'); + element.setAttribute('style', 'border-bottom-style: dotted'); expect(declaration.borderBottomStyle).toBe('dotted'); @@ -579,6 +727,10 @@ describe('CSSStyleDeclaration', () => { it('Returns style property.', () => { const declaration = new CSSStyleDeclaration(element); + element.setAttribute('style', 'border-left-style: inherit'); + + expect(declaration.borderLeftStyle).toBe('inherit'); + element.setAttribute('style', 'border-left-style: dotted'); expect(declaration.borderLeftStyle).toBe('dotted'); @@ -589,6 +741,274 @@ describe('CSSStyleDeclaration', () => { }); }); + describe('get borderRadius()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-radius: inherit'); + + expect(declaration.borderRadius).toBe('inherit'); + expect(declaration.borderTopLeftRadius).toBe('inherit'); + expect(declaration.borderTopRightRadius).toBe('inherit'); + expect(declaration.borderBottomRightRadius).toBe('inherit'); + expect(declaration.borderBottomLeftRadius).toBe('inherit'); + + element.setAttribute('style', 'border-radius: 1px 2px 3px 4px'); + + expect(declaration.borderRadius).toBe('1px 2px 3px 4px'); + expect(declaration.borderTopLeftRadius).toBe('1px'); + expect(declaration.borderTopRightRadius).toBe('2px'); + expect(declaration.borderBottomRightRadius).toBe('3px'); + expect(declaration.borderBottomLeftRadius).toBe('4px'); + + element.setAttribute('style', 'border-radius: 1px 2px 3px'); + + expect(declaration.borderRadius).toBe('1px 2px 3px'); + + element.setAttribute('style', 'border-radius: 1px 2px'); + + expect(declaration.borderRadius).toBe('1px 2px'); + + element.setAttribute('style', 'border-radius: 1px'); + + expect(declaration.borderRadius).toBe('1px'); + }); + }); + + describe('get borderTopLeftRadius()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-top-left-radius: inherit'); + + expect(declaration.borderTopLeftRadius).toBe('inherit'); + + element.setAttribute('style', 'border-top-left-radius: 1rem'); + + expect(declaration.borderTopLeftRadius).toBe('1rem'); + }); + }); + + describe('get borderTopRightRadius()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-top-right-radius: inherit'); + + expect(declaration.borderTopRightRadius).toBe('inherit'); + + element.setAttribute('style', 'border-top-right-radius: 1rem'); + + expect(declaration.borderTopRightRadius).toBe('1rem'); + }); + }); + + describe('get borderBottomRightRadius()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-bottom-right-radius: inherit'); + + expect(declaration.borderBottomRightRadius).toBe('inherit'); + + element.setAttribute('style', 'border-bottom-right-radius: 1rem'); + + expect(declaration.borderBottomRightRadius).toBe('1rem'); + }); + }); + + describe('get borderBottomLeftRadius()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'border-bottom-left-radius: inherit'); + + expect(declaration.borderBottomLeftRadius).toBe('inherit'); + + element.setAttribute('style', 'border-bottom-left-radius: 1rem'); + + expect(declaration.borderBottomLeftRadius).toBe('1rem'); + }); + }); + + describe('get borderCollapse()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + for (const value of ['collapse', 'separate', 'inherit']) { + element.setAttribute('style', `border-collapse: ${value}`); + + expect(declaration.borderCollapse).toBe(value); + } + }); + }); + + describe('get clear()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + for (const value of ['inherit', 'none', 'left', 'right', 'both']) { + element.setAttribute('style', `clear: ${value}`); + + expect(declaration.clear).toBe(value); + } + }); + }); + + describe('get clip()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'clip: inherit'); + + expect(declaration.clip).toBe('inherit'); + + element.setAttribute('style', 'clip: auto'); + + expect(declaration.clip).toBe('auto'); + + element.setAttribute('style', 'clip: rect(1px, 10em, 3rem, 2ch)'); + + expect(declaration.clip).toBe('rect(1px, 10em, 3rem, 2ch)'); + }); + }); + + describe('get cssFloat()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + for (const value of ['inherit', 'none', 'left', 'right', 'inline-start', 'inline-end']) { + element.setAttribute('style', `css-float: ${value}`); + + expect(declaration.cssFloat).toBe(value); + } + }); + }); + + describe('get float()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + for (const value of ['inherit', 'none', 'left', 'right', 'inline-start', 'inline-end']) { + element.setAttribute('style', `float: ${value}`); + + expect(declaration.float).toBe(value); + } + }); + }); + + describe('get display()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + for (const value of [ + 'inherit', + 'initial', + 'revert', + 'unset', + 'block', + 'inline', + 'inline-block', + 'flex', + 'inline-flex', + 'grid', + 'inline-grid', + 'flow-root', + 'none', + 'contents', + 'block flow', + 'inline flow', + 'inline flow-root', + 'block flex', + 'inline flex', + 'block grid', + 'inline grid', + 'block flow-root', + 'table', + 'table-row', + 'list-item' + ]) { + element.setAttribute('style', `display: ${value}`); + + expect(declaration.display).toBe(value); + } + }); + }); + + describe('get direction()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + for (const value of ['inherit', 'initial', 'revert', 'unset', 'ltr', 'rtl']) { + element.setAttribute('style', `direction: ${value}`); + + expect(declaration.direction).toBe(value); + } + }); + }); + + describe('get flex()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'flex: inherit'); + + expect(declaration.flex).toBe('inherit'); + expect(declaration.flexGrow).toBe('inherit'); + expect(declaration.flexShrink).toBe('inherit'); + expect(declaration.flexBasis).toBe('inherit'); + + element.setAttribute('style', 'flex: none'); + + expect(declaration.flex).toBe('0 0 auto'); + expect(declaration.flexGrow).toBe('0'); + expect(declaration.flexShrink).toBe('0'); + expect(declaration.flexBasis).toBe('auto'); + + element.setAttribute('style', 'flex: auto'); + + expect(declaration.flex).toBe('1 1 auto'); + expect(declaration.flexGrow).toBe('1'); + expect(declaration.flexShrink).toBe('1'); + expect(declaration.flexBasis).toBe('auto'); + + element.setAttribute('style', 'flex: fit-content(10px)'); + + expect(declaration.flex).toBe('1 1 fit-content(10px)'); + expect(declaration.flexGrow).toBe('1'); + expect(declaration.flexShrink).toBe('1'); + expect(declaration.flexBasis).toBe('fit-content(10px)'); + + element.setAttribute('style', 'flex: 3'); + + expect(declaration.flex).toBe('3 1 0%'); + expect(declaration.flexGrow).toBe('3'); + expect(declaration.flexShrink).toBe('1'); + expect(declaration.flexBasis).toBe('0%'); + + element.setAttribute('style', 'flex: 3 2'); + + expect(declaration.flex).toBe('3 2 0%'); + expect(declaration.flexGrow).toBe('3'); + expect(declaration.flexShrink).toBe('2'); + expect(declaration.flexBasis).toBe('0%'); + + element.setAttribute('style', 'flex: 3 2 min-content'); + + expect(declaration.flex).toBe('3 2 min-content'); + expect(declaration.flexGrow).toBe('3'); + expect(declaration.flexShrink).toBe('2'); + expect(declaration.flexBasis).toBe('min-content'); + + element.setAttribute('style', 'flex: 3 2 50rem'); + + expect(declaration.flex).toBe('3 2 50rem'); + expect(declaration.flexGrow).toBe('3'); + expect(declaration.flexShrink).toBe('2'); + expect(declaration.flexBasis).toBe('50rem'); + }); + }); + describe('get length()', () => { it('Returns length when of styles on element.', () => { const declaration = new CSSStyleDeclaration(element); From ca6c2aa1cb06a321e80a584ede85ecc9bb74d58e Mon Sep 17 00:00:00 2001 From: David Ortner Date: Tue, 20 Sep 2022 00:58:03 +0200 Subject: [PATCH 40/84] #344@trivial: Continues on CSSStyleDeclaration. --- .../CSSStyleDeclarationPropertyGetParser.ts | 38 +- .../CSSStyleDeclarationPropertyManager.ts | 12 +- .../CSSStyleDeclarationPropertySetParser.ts | 185 +++--- .../CSSStyleDeclarationValueParser.ts | 30 +- .../declaration/CSSStyleDeclaration.test.ts | 545 +++++++++++++++++- 5 files changed, 703 insertions(+), 107 deletions(-) diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts index 4916666bd..6f85c7632 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyGetParser.ts @@ -384,21 +384,13 @@ export default class CSSStyleDeclarationPropertyGetParser { values.push(properties['background-image'].value); } - if (!CSSStyleDeclarationValueParser.getInitial(properties['background-repeat'].value)) { - values.push(properties['background-repeat'].value); - } - - if (!CSSStyleDeclarationValueParser.getInitial(properties['background-attachment'].value)) { - values.push(properties['background-attachment'].value); - } - if ( !CSSStyleDeclarationValueParser.getInitial(properties['background-position-x'].value) && !CSSStyleDeclarationValueParser.getInitial(properties['background-position-y'].value) && - !CSSStyleDeclarationValueParser.getInitial(properties['background-position-size'].value) + !CSSStyleDeclarationValueParser.getInitial(properties['background-size'].value) ) { values.push( - `${properties['background-position-x'].value} ${properties['background-position-y'].value} / ${properties['background-position-size'].value}` + `${properties['background-position-x'].value} ${properties['background-position-y'].value} / ${properties['background-size'].value}` ); } else if ( !CSSStyleDeclarationValueParser.getInitial(properties['background-position-x'].value) && @@ -409,6 +401,22 @@ export default class CSSStyleDeclarationPropertyGetParser { ); } + if (!CSSStyleDeclarationValueParser.getInitial(properties['background-repeat'].value)) { + values.push(properties['background-repeat'].value); + } + + if (!CSSStyleDeclarationValueParser.getInitial(properties['background-attachment'].value)) { + values.push(properties['background-attachment'].value); + } + + if (!CSSStyleDeclarationValueParser.getInitial(properties['background-origin'].value)) { + values.push(properties['background-origin'].value); + } + + if (!CSSStyleDeclarationValueParser.getInitial(properties['background-clip'].value)) { + values.push(properties['background-clip'].value); + } + if (!CSSStyleDeclarationValueParser.getInitial(properties['background-color'].value)) { values.push(properties['background-color'].value); } @@ -452,9 +460,17 @@ export default class CSSStyleDeclarationPropertyGetParser { }; } + const positionX = properties['background-position-x'].value.replace(/ *, */g, ',').split(','); + const positionY = properties['background-position-y'].value.replace(/ *, */g, ',').split(','); + const parts = []; + + for (let i = 0; i < positionX.length; i++) { + parts.push(`${positionX[i]} ${positionY[i]}`); + } + return { important, - value: `${properties['background-position-x'].value} ${properties['background-position-y'].value}` + value: parts.join(', ') }; } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts index ea645febe..ac47171f2 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts @@ -388,30 +388,30 @@ export default class CSSStyleDeclarationPropertyManager { case 'padding-top': properties = CSSStyleDeclarationPropertySetParser.getPaddingTop(value, important); break; + case 'padding-right': + properties = CSSStyleDeclarationPropertySetParser.getPaddingRight(value, important); + break; case 'padding-bottom': properties = CSSStyleDeclarationPropertySetParser.getPaddingBottom(value, important); break; case 'padding-left': properties = CSSStyleDeclarationPropertySetParser.getPaddingLeft(value, important); break; - case 'padding-right': - properties = CSSStyleDeclarationPropertySetParser.getPaddingRight(value, important); - break; case 'margin': properties = CSSStyleDeclarationPropertySetParser.getMargin(value, important); break; case 'margin-top': properties = CSSStyleDeclarationPropertySetParser.getMarginTop(value, important); break; + case 'margin-right': + properties = CSSStyleDeclarationPropertySetParser.getMarginRight(value, important); + break; case 'margin-bottom': properties = CSSStyleDeclarationPropertySetParser.getMarginBottom(value, important); break; case 'margin-left': properties = CSSStyleDeclarationPropertySetParser.getMarginLeft(value, important); break; - case 'margin-right': - properties = CSSStyleDeclarationPropertySetParser.getMarginRight(value, important); - break; case 'background': properties = CSSStyleDeclarationPropertySetParser.getBackground(value, important); break; diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts index 5d140c1a8..ab7f49e8b 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts @@ -20,7 +20,7 @@ const BACKGROUND_REPEAT = ['repeat', 'repeat-x', 'repeat-y', 'no-repeat']; const BACKGROUND_ORIGIN = ['border-box', 'padding-box', 'content-box']; const BACKGROUND_CLIP = ['border-box', 'padding-box', 'content-box']; const BACKGROUND_ATTACHMENT = ['scroll', 'fixed']; -const FLEX_BASIS = ['auto', 'fill', 'max-content', 'min-content', 'fit-content', 'content']; +const FLEX_BASIS = ['auto', 'fill', 'content']; const CLEAR = ['none', 'left', 'right', 'both']; const FLOAT = ['none', 'left', 'right', 'inline-start', 'inline-end']; const SYSTEM_FONT = ['caption', 'icon', 'menu', 'message-box', 'small-caption', 'status-bar']; @@ -351,7 +351,7 @@ export default class CSSStyleDeclarationPropertySetParser { ...this.getBorderImage('initial', important) }; - const parts = value.split(/ +/); + const parts = value.replace(/ *, */g, ',').split(/ +/); for (const part of parts) { const width = this.getBorderWidth(part, important); @@ -714,7 +714,7 @@ export default class CSSStyleDeclarationPropertySetParser { for (const part of parts) { if ( !CSSStyleDeclarationValueParser.getInteger(part) && - !CSSStyleDeclarationValueParser.getContentMeasurement(part) + !CSSStyleDeclarationValueParser.getAutoMeasurement(part) ) { return null; } @@ -1547,7 +1547,7 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const margin = CSSStyleDeclarationValueParser.getGlobal(value) || - CSSStyleDeclarationValueParser.getContentMeasurement(value); + CSSStyleDeclarationValueParser.getAutoMeasurement(value); return margin ? { 'margin-top': { value: margin, important } } : null; } @@ -1564,7 +1564,7 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const margin = CSSStyleDeclarationValueParser.getGlobal(value) || - CSSStyleDeclarationValueParser.getContentMeasurement(value); + CSSStyleDeclarationValueParser.getAutoMeasurement(value); return margin ? { 'margin-right': { value: margin, important } } : null; } @@ -1581,7 +1581,7 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const margin = CSSStyleDeclarationValueParser.getGlobal(value) || - CSSStyleDeclarationValueParser.getContentMeasurement(value); + CSSStyleDeclarationValueParser.getAutoMeasurement(value); return margin ? { 'margin-bottom': { value: margin, important } } : null; } @@ -1598,7 +1598,7 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue } { const margin = CSSStyleDeclarationValueParser.getGlobal(value) || - CSSStyleDeclarationValueParser.getContentMeasurement(value); + CSSStyleDeclarationValueParser.getAutoMeasurement(value); return margin ? { 'margin-left': { value: margin, important } } : null; } @@ -1682,7 +1682,7 @@ export default class CSSStyleDeclarationPropertySetParser { if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || FLEX_BASIS.includes(lowerValue)) { return { 'flex-basis': { value: lowerValue, important } }; } - const measurement = CSSStyleDeclarationValueParser.getContentMeasurement(value); + const measurement = CSSStyleDeclarationValueParser.getContentMeasurement(lowerValue); return measurement ? { 'flex-basis': { value: measurement, important } } : null; } @@ -1701,7 +1701,7 @@ export default class CSSStyleDeclarationPropertySetParser { } { const parsedValue = CSSStyleDeclarationValueParser.getGlobal(value) || - CSSStyleDeclarationValueParser.getInteger(value); + CSSStyleDeclarationValueParser.getFloat(value); return parsedValue ? { 'flex-shrink': { value: parsedValue, important } } : null; } @@ -1720,7 +1720,7 @@ export default class CSSStyleDeclarationPropertySetParser { } { const parsedValue = CSSStyleDeclarationValueParser.getGlobal(value) || - CSSStyleDeclarationValueParser.getInteger(value); + CSSStyleDeclarationValueParser.getFloat(value); return parsedValue ? { 'flex-grow': { value: parsedValue, important } } : null; } @@ -1762,49 +1762,80 @@ export default class CSSStyleDeclarationPropertySetParser { ...this.getBackgroundColor('initial', important) }; - const parts = value.replace(/[ ]*\/[ ]*/g, '/').split(/ +/); + const parts = value + .replace(/[ ]*,[ ]*/g, ',') + .replace(/[ ]*\/[ ]*/g, '/') + .split(/ +/); + + const backgroundPositions = []; for (const part of parts) { - if (part.includes('/')) { + if (!part.startsWith('url') && part.includes('/')) { const [position, size] = part.split('/'); - const backgroundPosition = properties['background-position-x'] - ? this.getBackgroundPositionY(position, important) - : this.getBackgroundPosition(position, important); + const backgroundPositionX = this.getBackgroundPositionX(position, important); + const backgroundPositionY = this.getBackgroundPositionY(position, important); + const backgroundSize = this.getBackgroundSize(size, important); - if (!backgroundPosition || !backgroundSize) { + if ((!backgroundPositionX && !backgroundPositionY) || !backgroundSize) { return null; } - Object.assign(properties, backgroundPosition, backgroundSize); + if (backgroundPositionY) { + backgroundPositions.push(backgroundPositionY['background-position-y'].value); + } else if (backgroundPositionX) { + backgroundPositions.push(backgroundPositionX['background-position-x'].value); + } + + Object.assign(properties, backgroundSize); } else { const backgroundImage = this.getBackgroundImage(part, important); const backgroundRepeat = this.getBackgroundRepeat(part, important); const backgroundAttachment = this.getBackgroundAttachment(part, important); - const backgroundPosition = this.getBackgroundPosition(part, important); + const backgroundPositionX = this.getBackgroundPositionX(part, important); + const backgroundPositionY = this.getBackgroundPositionY(part, important); const backgroundColor = this.getBackgroundColor(part, important); + const backgroundOrigin = this.getBackgroundOrigin(part, important); + const backgroundClip = this.getBackgroundClip(part, important); if ( !backgroundImage && !backgroundRepeat && !backgroundAttachment && - !backgroundPosition && - !backgroundColor + !backgroundPositionX && + !backgroundPositionY && + !backgroundColor && + !backgroundOrigin && + !backgroundClip ) { return null; } + if (backgroundPositionX) { + backgroundPositions.push(backgroundPositionX['background-position-x'].value); + } else if (backgroundPositionY) { + backgroundPositions.push(backgroundPositionY['background-position-y'].value); + } + Object.assign( properties, backgroundImage, backgroundRepeat, backgroundAttachment, - backgroundPosition, - backgroundColor + backgroundColor, + backgroundOrigin, + backgroundClip ); } } + if (backgroundPositions.length) { + Object.assign( + properties, + this.getBackgroundPosition(backgroundPositions.join(' '), important) + ); + } + return properties; } @@ -1838,15 +1869,15 @@ export default class CSSStyleDeclarationPropertySetParser { if ( parts[0] !== 'cover' && parts[0] !== 'contain' && - !CSSStyleDeclarationValueParser.getContentMeasurement(parts[0]) + !CSSStyleDeclarationValueParser.getAutoMeasurement(parts[0]) ) { return null; } parsed.push(parts[0]); } else { if ( - !CSSStyleDeclarationValueParser.getContentMeasurement(parts[0]) || - !CSSStyleDeclarationValueParser.getContentMeasurement(parts[1]) + !CSSStyleDeclarationValueParser.getAutoMeasurement(parts[0]) || + !CSSStyleDeclarationValueParser.getAutoMeasurement(parts[1]) ) { return null; } @@ -1972,7 +2003,7 @@ export default class CSSStyleDeclarationPropertySetParser { }; } - const imageParts = value.split(','); + const imageParts = value.replace(/ *, */g, ',').split(','); let x = ''; let y = ''; @@ -1984,35 +2015,53 @@ export default class CSSStyleDeclarationPropertySetParser { y += ','; } - if (parts.length === 1) { - if (parts[0] === 'top' || parts[0] === 'bottom') { - x += 'center'; - y += parts[0]; - } else if (parts[0] === 'left' || parts[0] === 'right') { - x += parts[0]; - y += 'center'; - } else if (parts[0] === 'center') { - x += 'center'; - y += 'center'; - } - } - - if (parts.length !== 2 && parts.length !== 4) { - return null; - } - - if (parts.length === 2) { - x += parts[0] === 'top' || parts[0] === 'bottom' ? parts[1] : parts[0]; - y += parts[0] === 'left' || parts[0] === 'right' ? parts[0] : parts[1]; - } else if (parts.length === 4) { - x += - parts[0] === 'top' || parts[0] === 'bottom' || parts[1] === 'top' || parts[1] === 'bottom' - ? `${parts[2]} ${parts[3]}` - : `${parts[0]} ${parts[1]}`; - y += - parts[2] === 'left' || parts[2] === 'right' || parts[3] === 'left' || parts[3] === 'right' - ? `${parts[0]} ${parts[1]}` - : `${parts[2]} ${parts[3]}`; + switch (parts.length) { + case 1: + if (parts[0] === 'top' || parts[0] === 'bottom') { + x += 'center'; + y += parts[0]; + } else if (parts[0] === 'left' || parts[0] === 'right') { + x += parts[0]; + y += 'center'; + } else if (parts[0] === 'center') { + x += 'center'; + y += 'center'; + } + break; + case 2: + x += parts[0] === 'top' || parts[0] === 'bottom' ? parts[1] : parts[0]; + y += parts[0] === 'top' || parts[0] === 'bottom' ? parts[0] : parts[1]; + break; + case 3: + if ( + (parts[0] === 'top' || parts[0] === 'bottom') && + (parts[2] === 'left' || parts[2] === 'right') + ) { + x += `${parts[1]} ${parts[2]}`; + y += parts[0]; + } else { + x += parts[0]; + y += `${parts[1]} ${parts[2]}`; + } + break; + case 4: + x += + parts[0] === 'top' || + parts[0] === 'bottom' || + parts[1] === 'top' || + parts[1] === 'bottom' + ? `${parts[2]} ${parts[3]}` + : `${parts[0]} ${parts[1]}`; + y += + parts[0] === 'top' || + parts[0] === 'bottom' || + parts[1] === 'top' || + parts[1] === 'bottom' + ? `${parts[0]} ${parts[1]}` + : `${parts[2]} ${parts[3]}`; + break; + default: + return null; } } @@ -2048,7 +2097,7 @@ export default class CSSStyleDeclarationPropertySetParser { return { 'background-position-x': { value: lowerValue, important } }; } - const imageParts = lowerValue.split(','); + const imageParts = lowerValue.replace(/ *, */g, ',').split(','); let parsedValue = ''; for (const imagePart of imageParts) { @@ -2059,12 +2108,8 @@ export default class CSSStyleDeclarationPropertySetParser { } for (const part of parts) { - if ( - !CSSStyleDeclarationValueParser.getMeasurement(part) && - part !== 'left' && - part !== 'right' && - part !== 'center' - ) { + const measurement = CSSStyleDeclarationValueParser.getMeasurement(part); + if (!measurement && part !== 'left' && part !== 'right' && part !== 'center') { return null; } @@ -2072,7 +2117,7 @@ export default class CSSStyleDeclarationPropertySetParser { parsedValue += ' '; } - parsedValue += part; + parsedValue += measurement || part; } } @@ -2098,7 +2143,7 @@ export default class CSSStyleDeclarationPropertySetParser { return { 'background-position-y': { value: lowerValue, important } }; } - const imageParts = lowerValue.split(','); + const imageParts = lowerValue.replace(/ *, */g, ',').split(','); let parsedValue = ''; for (const imagePart of imageParts) { @@ -2109,12 +2154,8 @@ export default class CSSStyleDeclarationPropertySetParser { } for (const part of parts) { - if ( - !CSSStyleDeclarationValueParser.getMeasurement(part) && - part !== 'top' && - part !== 'bottom' && - part !== 'center' - ) { + const measurement = CSSStyleDeclarationValueParser.getMeasurement(part); + if (!measurement && part !== 'top' && part !== 'bottom' && part !== 'center') { return null; } @@ -2122,7 +2163,7 @@ export default class CSSStyleDeclarationPropertySetParser { parsedValue += ' '; } - parsedValue += part; + parsedValue += measurement || part; } } @@ -2168,7 +2209,7 @@ export default class CSSStyleDeclarationPropertySetParser { return { 'background-image': { value: lowerValue, important } }; } - const parts = value.split(','); + const parts = value.replace(/ *, */g, ',').split(','); const parsed = []; for (const part of parts) { diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts index 746fd7bc0..4bf0e09a9 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts @@ -232,7 +232,12 @@ export default class CSSStyleDeclarationValueParser { */ public static getFitContent(value: string): string { const lowerValue = value.toLowerCase(); - if (lowerValue === 'auto' || lowerValue === 'max-content' || lowerValue === 'min-content') { + if ( + lowerValue === 'auto' || + lowerValue === 'max-content' || + lowerValue === 'min-content' || + lowerValue === 'fit-content' + ) { return lowerValue; } if (FIT_CONTENT_REGEXP.test(lowerValue)) { @@ -261,6 +266,19 @@ export default class CSSStyleDeclarationValueParser { return this.getFitContent(value) || this.getMeasurement(value); } + /** + * Returns measurement or auto, min-content, max-content or fit-content. + * + * @param value Value. + * @returns Parsed value. + */ + public static getAutoMeasurement(value: string): string { + if (value.toLocaleLowerCase() === 'auto') { + return 'auto'; + } + return this.getMeasurement(value); + } + /** * Returns integer. * @@ -312,7 +330,7 @@ export default class CSSStyleDeclarationValueParser { return lowerValue; } if (COLOR_REGEXP.test(value)) { - return value; + return value.replace(/,([^ ])/g, ', $1'); } return null; } @@ -331,10 +349,8 @@ export default class CSSStyleDeclarationValueParser { return null; } - const lowerValue = value.toLowerCase(); - - if (lowerValue === 'none' || lowerValue === 'inherit') { - return lowerValue; + if (value.toLowerCase() === 'none') { + return 'none'; } const result = URL_REGEXP.exec(value); @@ -369,7 +385,7 @@ export default class CSSStyleDeclarationValueParser { } } - return value; + return `url("${url}")`; } /** diff --git a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts index bf6f9113c..1ccd09fbd 100644 --- a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts +++ b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts @@ -160,6 +160,31 @@ describe('CSSStyleDeclaration', () => { element.setAttribute('style', 'border: green solid'); expect(declaration.border).toBe('solid green'); + + element.setAttribute('style', 'border: 2px solid rgb(255, 255, 255)'); + + expect(declaration.border).toBe('2px solid rgb(255, 255, 255)'); + + expect(declaration.borderTop).toBe('2px solid rgb(255, 255, 255)'); + expect(declaration.borderRight).toBe('2px solid rgb(255, 255, 255)'); + expect(declaration.borderBottom).toBe('2px solid rgb(255, 255, 255)'); + expect(declaration.borderLeft).toBe('2px solid rgb(255, 255, 255)'); + + expect(declaration.borderTopColor).toBe('rgb(255, 255, 255)'); + expect(declaration.borderTopWidth).toBe('2px'); + expect(declaration.borderTopStyle).toBe('solid'); + + expect(declaration.borderRightColor).toBe('rgb(255, 255, 255)'); + expect(declaration.borderRightWidth).toBe('2px'); + expect(declaration.borderRightStyle).toBe('solid'); + + expect(declaration.borderBottomColor).toBe('rgb(255, 255, 255)'); + expect(declaration.borderBottomWidth).toBe('2px'); + expect(declaration.borderBottomStyle).toBe('solid'); + + expect(declaration.borderLeftColor).toBe('rgb(255, 255, 255)'); + expect(declaration.borderLeftWidth).toBe('2px'); + expect(declaration.borderLeftStyle).toBe('solid'); }); }); @@ -336,15 +361,15 @@ describe('CSSStyleDeclaration', () => { expect(declaration.borderTopColor).toBe('#000'); expect(declaration.borderRightColor).toBe('#ffffff'); - expect(declaration.borderBottomColor).toBe('rgba(135,200,150,0.5)'); + expect(declaration.borderBottomColor).toBe('rgba(135, 200, 150, 0.5)'); expect(declaration.borderLeftColor).toBe('blue'); element.setAttribute('style', 'border-color: rgb(135,200,150)'); - expect(declaration.borderTopColor).toBe('rgb(135,200,150)'); - expect(declaration.borderRightColor).toBe('rgb(135,200,150)'); - expect(declaration.borderBottomColor).toBe('rgb(135,200,150)'); - expect(declaration.borderLeftColor).toBe('rgb(135,200,150)'); + expect(declaration.borderTopColor).toBe('rgb(135, 200, 150)'); + expect(declaration.borderRightColor).toBe('rgb(135, 200, 150)'); + expect(declaration.borderBottomColor).toBe('rgb(135, 200, 150)'); + expect(declaration.borderLeftColor).toBe('rgb(135, 200, 150)'); }); }); @@ -372,7 +397,7 @@ describe('CSSStyleDeclaration', () => { element.setAttribute('style', `border-image: url('/media/examples/border-diamonds.png') 30`); expect(declaration.borderImage).toBe( - `url('/media/examples/border-diamonds.png') 30 / 1 / 0 stretch` + `url("/media/examples/border-diamonds.png") 30 / 1 / 0 stretch` ); element.setAttribute( @@ -381,18 +406,18 @@ describe('CSSStyleDeclaration', () => { ); expect(declaration.borderImage).toBe( - `url('/media/examples/border-diamonds.png') 30 / 19px / 0 round` + `url("/media/examples/border-diamonds.png") 30 / 19px / 0 round` ); element.setAttribute( 'style', - `border-image: url('/media/examples/border-diamonds.png') 10 fill / 20px / 30px space` + `border-image: url("/media/examples/border-diamonds.png") 10 fill / 20px / 30px space` ); expect(declaration.borderImage).toBe( - `url('/media/examples/border-diamonds.png') 10 fill / 20px / 30px space` + `url("/media/examples/border-diamonds.png") 10 fill / 20px / 30px space` ); - expect(declaration.borderImageSource).toBe(`url('/media/examples/border-diamonds.png')`); + expect(declaration.borderImageSource).toBe(`url("/media/examples/border-diamonds.png")`); expect(declaration.borderImageOutset).toBe('30px'); expect(declaration.borderImageRepeat).toBe('space'); expect(declaration.borderImageSlice).toBe('10 fill'); @@ -417,7 +442,7 @@ describe('CSSStyleDeclaration', () => { `border-image-source: url('/media/examples/border-diamonds.png')` ); - expect(declaration.borderImageSource).toBe(`url('/media/examples/border-diamonds.png')`); + expect(declaration.borderImageSource).toBe(`url("/media/examples/border-diamonds.png")`); element.setAttribute('style', `border-image-source: NONE`); @@ -1009,6 +1034,504 @@ describe('CSSStyleDeclaration', () => { }); }); + describe('get flexShrink()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'flex-shrink: inherit'); + + expect(declaration.flexShrink).toBe('inherit'); + + element.setAttribute('style', 'flex-shrink: 2'); + + expect(declaration.flexShrink).toBe('2'); + + element.setAttribute('style', 'flex-shrink: 0.6'); + + expect(declaration.flexShrink).toBe('0.6'); + }); + }); + + describe('get flexGrow()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'flex-grow: inherit'); + + expect(declaration.flexGrow).toBe('inherit'); + + element.setAttribute('style', 'flex-grow: 2'); + + expect(declaration.flexGrow).toBe('2'); + + element.setAttribute('style', 'flex-grow: 0.6'); + + expect(declaration.flexGrow).toBe('0.6'); + }); + }); + + describe('get flexBasis()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'flex-basis: 10em'); + + expect(declaration.flexBasis).toBe('10em'); + + element.setAttribute('style', 'flex-basis: fit-content(10px)'); + + expect(declaration.flexBasis).toBe('fit-content(10px)'); + + for (const value of [ + 'inherit', + 'initial', + 'revert', + 'unset', + 'auto', + 'fill', + 'content', + 'max-content', + 'min-content', + 'fit-content' + ]) { + element.setAttribute('style', `flex-basis: ${value}`); + + expect(declaration.flexBasis).toBe(value); + } + }); + }); + + describe('get padding()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'padding: inherit'); + + expect(declaration.padding).toBe('inherit'); + + element.setAttribute('style', 'padding: 1px 2px 3px 4px'); + + expect(declaration.padding).toBe('1px 2px 3px 4px'); + + element.setAttribute('style', 'padding: 1px 2px 3px'); + + expect(declaration.padding).toBe('1px 2px 3px'); + + element.setAttribute('style', 'padding: 1px 2px'); + + expect(declaration.padding).toBe('1px 2px'); + + element.setAttribute('style', 'padding: 1px'); + + expect(declaration.padding).toBe('1px'); + + element.setAttribute('style', 'padding: auto'); + + expect(declaration.padding).toBe(''); + }); + }); + + describe('get paddingTop()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'padding-top: inherit'); + + expect(declaration.paddingTop).toBe('inherit'); + + element.setAttribute('style', 'padding-top: 1px'); + + expect(declaration.paddingTop).toBe('1px'); + + element.setAttribute('style', 'padding-top: 1%'); + + expect(declaration.paddingTop).toBe('1%'); + }); + }); + + describe('get paddingRight()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'padding-right: inherit'); + + expect(declaration.paddingRight).toBe('inherit'); + + element.setAttribute('style', 'padding-right: 1px'); + + expect(declaration.paddingRight).toBe('1px'); + + element.setAttribute('style', 'padding-right: 1%'); + + expect(declaration.paddingRight).toBe('1%'); + }); + }); + + describe('get paddingBottom()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'padding-bottom: inherit'); + + expect(declaration.paddingBottom).toBe('inherit'); + + element.setAttribute('style', 'padding-bottom: 1px'); + + expect(declaration.paddingBottom).toBe('1px'); + + element.setAttribute('style', 'padding-bottom: 1%'); + + expect(declaration.paddingBottom).toBe('1%'); + }); + }); + + describe('get paddingLeft()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'padding-left: inherit'); + + expect(declaration.paddingLeft).toBe('inherit'); + + element.setAttribute('style', 'padding-left: 1px'); + + expect(declaration.paddingLeft).toBe('1px'); + + element.setAttribute('style', 'padding-left: 1%'); + + expect(declaration.paddingLeft).toBe('1%'); + }); + }); + + describe('get margin()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'margin: inherit'); + + expect(declaration.margin).toBe('inherit'); + + element.setAttribute('style', 'margin: 1px 2px 3px 4px'); + + expect(declaration.margin).toBe('1px 2px 3px 4px'); + + element.setAttribute('style', 'margin: 1px 2px 3px'); + + expect(declaration.margin).toBe('1px 2px 3px'); + + element.setAttribute('style', 'margin: 1px 2px'); + + expect(declaration.margin).toBe('1px 2px'); + + element.setAttribute('style', 'margin: 1px'); + + expect(declaration.margin).toBe('1px'); + + element.setAttribute('style', 'margin: auto'); + + expect(declaration.margin).toBe('auto'); + }); + }); + + describe('get marginTop()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'margin-top: inherit'); + + expect(declaration.marginTop).toBe('inherit'); + + element.setAttribute('style', 'margin-top: 1px'); + + expect(declaration.marginTop).toBe('1px'); + + element.setAttribute('style', 'margin-top: 1%'); + + expect(declaration.marginTop).toBe('1%'); + + element.setAttribute('style', 'margin-top: auto'); + + expect(declaration.marginTop).toBe('auto'); + }); + }); + + describe('get marginRight()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'margin-right: inherit'); + + expect(declaration.marginRight).toBe('inherit'); + + element.setAttribute('style', 'margin-right: 1px'); + + expect(declaration.marginRight).toBe('1px'); + + element.setAttribute('style', 'margin-right: 1%'); + + expect(declaration.marginRight).toBe('1%'); + + element.setAttribute('style', 'margin-right: auto'); + + expect(declaration.marginRight).toBe('auto'); + }); + }); + + describe('get marginBottom()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'margin-bottom: inherit'); + + expect(declaration.marginBottom).toBe('inherit'); + + element.setAttribute('style', 'margin-bottom: 1px'); + + expect(declaration.marginBottom).toBe('1px'); + + element.setAttribute('style', 'margin-bottom: 1%'); + + expect(declaration.marginBottom).toBe('1%'); + + element.setAttribute('style', 'margin-bottom: auto'); + + expect(declaration.marginBottom).toBe('auto'); + }); + }); + + describe('get marginLeft()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'margin-left: inherit'); + + expect(declaration.marginLeft).toBe('inherit'); + + element.setAttribute('style', 'margin-left: 1px'); + + expect(declaration.marginLeft).toBe('1px'); + + element.setAttribute('style', 'margin-left: 1%'); + + expect(declaration.marginLeft).toBe('1%'); + + element.setAttribute('style', 'margin-left: auto'); + + expect(declaration.marginLeft).toBe('auto'); + }); + }); + + describe('get background()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'background: inherit'); + + expect(declaration.background).toBe('inherit'); + expect(declaration.backgroundAttachment).toBe('inherit'); + expect(declaration.backgroundClip).toBe('inherit'); + expect(declaration.backgroundColor).toBe('inherit'); + expect(declaration.backgroundImage).toBe('inherit'); + expect(declaration.backgroundPosition).toBe('inherit'); + expect(declaration.backgroundRepeat).toBe('inherit'); + expect(declaration.backgroundSize).toBe('inherit'); + + element.setAttribute('style', 'background: green'); + + expect(declaration.background).toBe('green'); + + element.setAttribute('style', 'background: rgb(255, 255, 255)'); + + expect(declaration.background).toBe('rgb(255, 255, 255)'); + + element.setAttribute('style', 'background: url("test.jpg") repeat-y'); + + expect(declaration.background).toBe('url("test.jpg") repeat-y'); + + element.setAttribute('style', 'background: border-box red'); + + expect(declaration.background).toBe('border-box border-box red'); + + element.setAttribute('style', 'background: no-repeat center/80% url("../img/image.png")'); + + expect(declaration.background).toBe('url("../img/image.png") center center / 80% no-repeat'); + + element.setAttribute( + 'style', + 'background: scroll no-repeat top center / 80% url("../img/image.png")' + ); + + expect(declaration.background).toBe( + 'url("../img/image.png") center top / 80% no-repeat scroll' + ); + }); + }); + + describe('get backgroundImage()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'background-image: inherit'); + + expect(declaration.backgroundImage).toBe('inherit'); + + element.setAttribute('style', 'background-image: url("test.jpg")'); + + expect(declaration.backgroundImage).toBe('url("test.jpg")'); + + element.setAttribute('style', 'background-image: url(test.jpg)'); + + expect(declaration.backgroundImage).toBe('url("test.jpg")'); + + element.setAttribute('style', 'background-image: url(test.jpg), url(test2.jpg)'); + + expect(declaration.backgroundImage).toBe('url("test.jpg"), url("test2.jpg")'); + }); + }); + + describe('get backgroundColor()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'background-color: inherit'); + + expect(declaration.backgroundColor).toBe('inherit'); + + element.setAttribute('style', 'background-color: red'); + + expect(declaration.backgroundColor).toBe('red'); + + element.setAttribute('style', 'background-color: rgba(0, 55, 1,0.5)'); + + expect(declaration.backgroundColor).toBe('rgba(0, 55, 1, 0.5)'); + }); + }); + + describe('get backgroundRepeat()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + for (const repeat of [ + 'inherit', + 'initial', + 'revert', + 'unset', + 'repeat', + 'repeat-x', + 'repeat-y', + 'no-repeat' + ]) { + element.setAttribute('style', `background-repeat: ${repeat}`); + expect(declaration.backgroundRepeat).toBe(repeat); + } + }); + }); + + describe('get backgroundAttachment()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + for (const repeat of ['inherit', 'initial', 'revert', 'unset', 'scroll', 'fixed']) { + element.setAttribute('style', `background-attachment: ${repeat}`); + expect(declaration.backgroundAttachment).toBe(repeat); + } + }); + }); + + describe('get backgroundPosition()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'background-position: inherit'); + + expect(declaration.backgroundPosition).toBe('inherit'); + + element.setAttribute('style', 'background-position: top'); + + expect(declaration.backgroundPosition).toBe('center top'); + expect(declaration.backgroundPositionX).toBe('center'); + expect(declaration.backgroundPositionY).toBe('top'); + + element.setAttribute('style', 'background-position: bottom'); + + expect(declaration.backgroundPosition).toBe('center bottom'); + expect(declaration.backgroundPositionX).toBe('center'); + expect(declaration.backgroundPositionY).toBe('bottom'); + + element.setAttribute('style', 'background-position: left'); + + expect(declaration.backgroundPosition).toBe('left center'); + expect(declaration.backgroundPositionX).toBe('left'); + expect(declaration.backgroundPositionY).toBe('center'); + + element.setAttribute('style', 'background-position: right'); + + expect(declaration.backgroundPosition).toBe('right center'); + expect(declaration.backgroundPositionX).toBe('right'); + expect(declaration.backgroundPositionY).toBe('center'); + + element.setAttribute('style', 'background-position: center'); + + expect(declaration.backgroundPosition).toBe('center center'); + expect(declaration.backgroundPositionX).toBe('center'); + expect(declaration.backgroundPositionY).toBe('center'); + + element.setAttribute('style', 'background-position: 25% 75%'); + + expect(declaration.backgroundPosition).toBe('25% 75%'); + expect(declaration.backgroundPositionX).toBe('25%'); + expect(declaration.backgroundPositionY).toBe('75%'); + + element.setAttribute('style', 'background-position: 0 0'); + + expect(declaration.backgroundPosition).toBe('0px 0px'); + expect(declaration.backgroundPositionX).toBe('0px'); + expect(declaration.backgroundPositionY).toBe('0px'); + + element.setAttribute('style', 'background-position: 1cm 2cm'); + + expect(declaration.backgroundPosition).toBe('1cm 2cm'); + expect(declaration.backgroundPositionX).toBe('1cm'); + expect(declaration.backgroundPositionY).toBe('2cm'); + + element.setAttribute('style', 'background-position: 10ch 8em'); + + expect(declaration.backgroundPosition).toBe('10ch 8em'); + expect(declaration.backgroundPositionX).toBe('10ch'); + expect(declaration.backgroundPositionY).toBe('8em'); + + element.setAttribute('style', 'background-position: 0 0, center'); + + expect(declaration.backgroundPosition).toBe('0px 0px, center center'); + expect(declaration.backgroundPositionX).toBe('0px, center'); + expect(declaration.backgroundPositionY).toBe('0px, center'); + + element.setAttribute('style', 'background-position: bottom 10px right 20px'); + + expect(declaration.backgroundPosition).toBe('right 20px bottom 10px'); + expect(declaration.backgroundPositionX).toBe('right 20px'); + expect(declaration.backgroundPositionY).toBe('bottom 10px'); + + element.setAttribute('style', 'background-position: right 20px bottom 10px'); + + expect(declaration.backgroundPosition).toBe('right 20px bottom 10px'); + expect(declaration.backgroundPositionX).toBe('right 20px'); + expect(declaration.backgroundPositionY).toBe('bottom 10px'); + + element.setAttribute('style', 'background-position: bottom 10px right'); + + expect(declaration.backgroundPosition).toBe('right bottom 10px'); + expect(declaration.backgroundPositionX).toBe('right'); + expect(declaration.backgroundPositionY).toBe('bottom 10px'); + + element.setAttribute('style', 'background-position: top right 10px'); + + expect(declaration.backgroundPosition).toBe('right 10px top'); + expect(declaration.backgroundPositionX).toBe('right 10px'); + expect(declaration.backgroundPositionY).toBe('top'); + }); + }); + describe('get length()', () => { it('Returns length when of styles on element.', () => { const declaration = new CSSStyleDeclaration(element); From 91bbdf5d21802854bf057a86077dae7ce32080e6 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Wed, 21 Sep 2022 16:05:54 +0200 Subject: [PATCH 41/84] #344@trivial: Continue on CSSStyleDeclaration. --- .../CSSStyleDeclarationPropertyManager.ts | 6 +- .../CSSStyleDeclarationPropertySetParser.ts | 74 +++- .../declaration/CSSStyleDeclaration.test.ts | 322 ++++++++++++++++++ 3 files changed, 386 insertions(+), 16 deletions(-) diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts index ac47171f2..7c0421ba8 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts @@ -430,6 +430,9 @@ export default class CSSStyleDeclarationPropertyManager { case 'background-position': properties = CSSStyleDeclarationPropertySetParser.getBackgroundPosition(value, important); break; + case 'width': + properties = CSSStyleDeclarationPropertySetParser.getWidth(value, important); + break; case 'top': properties = CSSStyleDeclarationPropertySetParser.getTop(value, important); break; @@ -466,9 +469,6 @@ export default class CSSStyleDeclarationPropertyManager { case 'font-family': properties = CSSStyleDeclarationPropertySetParser.getFontFamily(value, important); break; - case 'font-size': - properties = CSSStyleDeclarationPropertySetParser.getFontSize(value, important); - break; case 'color': properties = CSSStyleDeclarationPropertySetParser.getColor(value, important); break; diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts index ab7f49e8b..cdb5058a0 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts @@ -153,6 +153,25 @@ export default class CSSStyleDeclarationPropertySetParser { return null; } + /** + * Returns width. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getWidth( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const parsedValue = + CSSStyleDeclarationValueParser.getGlobal(value) || + CSSStyleDeclarationValueParser.getContentMeasurement(value); + return parsedValue ? { width: { value: parsedValue, important } } : null; + } + /** * Returns top. * @@ -2034,14 +2053,28 @@ export default class CSSStyleDeclarationPropertySetParser { break; case 3: if ( - (parts[0] === 'top' || parts[0] === 'bottom') && - (parts[2] === 'left' || parts[2] === 'right') + parts[0] === 'top' || + parts[0] === 'bottom' || + parts[1] === 'left' || + parts[1] === 'right' || + parts[2] === 'left' || + parts[2] === 'right' ) { - x += `${parts[1]} ${parts[2]}`; - y += parts[0]; + if (CSSStyleDeclarationValueParser.getMeasurement(parts[1])) { + x += parts[2]; + y += `${parts[0]} ${parts[1]}`; + } else { + x += `${parts[1]} ${parts[2]}`; + y += parts[0]; + } } else { - x += parts[0]; - y += `${parts[1]} ${parts[2]}`; + if (CSSStyleDeclarationValueParser.getMeasurement(parts[1])) { + x += `${parts[0]} ${parts[1]}`; + y += parts[2]; + } else { + x += parts[0]; + y += `${parts[1]} ${parts[2]}`; + } } break; case 4: @@ -2509,23 +2542,38 @@ export default class CSSStyleDeclarationPropertySetParser { const parts = value.split(','); let parsedValue = ''; + let endWithApostroph = false; + for (let i = 0, max = parts.length; i < max; i++) { - const trimmedPart = parts[i].trim().replace(/'/g, '"'); - if ( - trimmedPart.includes(' ') && - (trimmedPart[0] !== '"' || trimmedPart[trimmedPart.length - 1] !== '"') - ) { - return null; - } + let trimmedPart = parts[i].trim().replace(/'/g, '"'); + if (!trimmedPart) { return null; } + + if (trimmedPart.includes(' ')) { + const apostrophCount = (trimmedPart.match(/"/g) || []).length; + if ((trimmedPart[0] !== '"' || i !== 0) && apostrophCount !== 2 && apostrophCount !== 0) { + return null; + } + if (trimmedPart[0] === '"' && trimmedPart[trimmedPart.length - 1] !== '"') { + endWithApostroph = true; + } else if (trimmedPart[0] !== '"' && trimmedPart[trimmedPart.length - 1] !== '"') { + trimmedPart = `"${trimmedPart}"`; + } + } + if (i > 0) { parsedValue += ', '; } + parsedValue += trimmedPart; } + if (endWithApostroph) { + parsedValue += '"'; + } + if (!parsedValue) { return null; } diff --git a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts index 1ccd09fbd..7b8ea9e18 100644 --- a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts +++ b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts @@ -1529,6 +1529,328 @@ describe('CSSStyleDeclaration', () => { expect(declaration.backgroundPosition).toBe('right 10px top'); expect(declaration.backgroundPositionX).toBe('right 10px'); expect(declaration.backgroundPositionY).toBe('top'); + + element.setAttribute('style', 'background-position: right 10px top'); + + expect(declaration.backgroundPosition).toBe('right 10px top'); + expect(declaration.backgroundPositionX).toBe('right 10px'); + expect(declaration.backgroundPositionY).toBe('top'); + + element.setAttribute('style', 'background-position: right top 10px'); + + expect(declaration.backgroundPosition).toBe('right top 10px'); + expect(declaration.backgroundPositionX).toBe('right'); + expect(declaration.backgroundPositionY).toBe('top 10px'); + }); + }); + + describe('get width()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'width: inherit'); + + expect(declaration.width).toBe('inherit'); + + element.setAttribute('style', 'width: 75%'); + + expect(declaration.width).toBe('75%'); + + element.setAttribute('style', 'width: 75px'); + + expect(declaration.width).toBe('75px'); + + element.setAttribute('style', 'width: fit-content(20em)'); + + expect(declaration.width).toBe('fit-content(20em)'); + }); + }); + + describe('get top()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'top: inherit'); + + expect(declaration.top).toBe('inherit'); + + element.setAttribute('style', 'top: 75%'); + + expect(declaration.top).toBe('75%'); + + element.setAttribute('style', 'top: 75px'); + + expect(declaration.top).toBe('75px'); + + element.setAttribute('style', 'top: fit-content(20em)'); + + expect(declaration.top).toBe('fit-content(20em)'); + }); + }); + + describe('get right()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'right: inherit'); + + expect(declaration.right).toBe('inherit'); + + element.setAttribute('style', 'right: 75%'); + + expect(declaration.right).toBe('75%'); + + element.setAttribute('style', 'right: 75px'); + + expect(declaration.right).toBe('75px'); + + element.setAttribute('style', 'right: fit-content(20em)'); + + expect(declaration.right).toBe('fit-content(20em)'); + }); + }); + + describe('get bottom()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'bottom: inherit'); + + expect(declaration.bottom).toBe('inherit'); + + element.setAttribute('style', 'bottom: 75%'); + + expect(declaration.bottom).toBe('75%'); + + element.setAttribute('style', 'bottom: 75px'); + + expect(declaration.bottom).toBe('75px'); + + element.setAttribute('style', 'bottom: fit-content(20em)'); + + expect(declaration.bottom).toBe('fit-content(20em)'); + }); + }); + + describe('get left()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'left: inherit'); + + expect(declaration.left).toBe('inherit'); + + element.setAttribute('style', 'left: 75%'); + + expect(declaration.left).toBe('75%'); + + element.setAttribute('style', 'left: 75px'); + + expect(declaration.left).toBe('75px'); + + element.setAttribute('style', 'left: fit-content(20em)'); + + expect(declaration.left).toBe('fit-content(20em)'); + }); + }); + + describe('get font()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', 'font: inherit'); + + expect(declaration.font).toBe('inherit'); + + element.setAttribute('style', 'font: 1.2em "Fira Sans", sans-serif'); + + expect(declaration.font).toBe('1.2em "Fira Sans", sans-serif'); + expect(declaration.fontFamily).toBe('"Fira Sans", sans-serif'); + expect(declaration.fontSize).toBe('1.2em'); + expect(declaration.fontStretch).toBe('normal'); + expect(declaration.fontStyle).toBe('normal'); + expect(declaration.fontVariant).toBe('normal'); + expect(declaration.fontWeight).toBe('normal'); + expect(declaration.lineHeight).toBe('normal'); + + element.setAttribute('style', 'font: italic 1.2em "Fira Sans", sans-serif'); + expect(declaration.font).toBe('italic 1.2em "Fira Sans", sans-serif'); + + element.setAttribute('style', 'font: 1.2em Fira Sans, sans-serif'); + expect(declaration.font).toBe('1.2em "Fira Sans", sans-serif'); + + element.setAttribute('style', 'font: 1.2em "Fira Sans, sans-serif'); + expect(declaration.font).toBe('1.2em "Fira Sans, sans-serif"'); + + element.setAttribute('style', 'font: 1.2em Fira "Sans, sans-serif'); + expect(declaration.font).toBe(''); + + element.setAttribute('style', 'font: italic small-caps bold 16px/2 cursive'); + expect(declaration.font).toBe('italic small-caps bold 16px / 2 cursive'); + + element.setAttribute('style', 'font: small-caps bold 24px/1 sans-serif'); + expect(declaration.font).toBe('small-caps bold 24px / 1 sans-serif'); + + element.setAttribute('style', 'font: caption'); + expect(declaration.font).toBe('caption'); + }); + }); + + describe('get fontStyle()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + for (const property of ['inherit', 'normal', 'italic', 'oblique', 'oblique 10deg']) { + element.setAttribute('style', `font-style: ${property}`); + expect(declaration.fontStyle).toBe(property); + } + }); + }); + + describe('get fontVariant()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + for (const property of ['inherit', 'normal', 'small-caps']) { + element.setAttribute('style', `font-variant: ${property}`); + expect(declaration.fontVariant).toBe(property); + } + }); + }); + + describe('get fontWeight()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + for (const property of [ + 'inherit', + 'normal', + 'bold', + 'bolder', + 'lighter', + '100', + '200', + '300', + '400', + '500', + '600', + '700', + '800', + '900' + ]) { + element.setAttribute('style', `font-weight: ${property}`); + expect(declaration.fontWeight).toBe(property); + } + }); + }); + + describe('get fontStretch()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + for (const property of [ + 'inherit', + 'normal', + 'ultra-condensed', + 'extra-condensed', + 'condensed', + 'semi-condensed', + 'semi-expanded', + 'expanded', + 'extra-expanded', + 'ultra-expanded' + ]) { + element.setAttribute('style', `font-stretch: ${property}`); + expect(declaration.fontStretch).toBe(property); + } + }); + }); + + describe('get fontSize()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + for (const property of [ + 'inherit', + 'medium', + 'xx-small', + 'x-small', + 'small', + 'large', + 'x-large', + 'xx-large', + 'smaller', + 'larger', + '10px', + '10em', + '10%' + ]) { + element.setAttribute('style', `font-size: ${property}`); + expect(declaration.fontSize).toBe(property); + } + }); + }); + + describe('get lineHeight()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + for (const property of ['inherit', 'normal', '10px', '10em', '10%', '10']) { + element.setAttribute('style', `line-height: ${property}`); + expect(declaration.lineHeight).toBe(property); + } + }); + }); + + describe('get fontFamily()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + for (const property of [ + 'inherit', + 'serif', + 'sans-serif', + 'cursive', + 'fantasy', + 'monospace' + ]) { + element.setAttribute('style', `font-family: ${property}`); + expect(declaration.fontFamily).toBe(property); + } + + element.setAttribute('style', 'font-family: "Fira Sans", sans-serif'); + expect(declaration.fontFamily).toBe('"Fira Sans", sans-serif'); + + element.setAttribute('style', 'font-family: Fira Sans, sans-serif'); + expect(declaration.fontFamily).toBe('"Fira Sans", sans-serif'); + + element.setAttribute('style', 'font-family: "Fira Sans, sans-serif'); + expect(declaration.fontFamily).toBe('"Fira Sans, sans-serif"'); + + element.setAttribute('style', 'font-family: Fira "Sans, sans-serif'); + expect(declaration.fontFamily).toBe(''); + }); + }); + + describe('get color()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + for (const property of ['inherit', 'red', 'rgb(255, 0, 0)', '#ff0000']) { + element.setAttribute('style', `color: ${property}`); + expect(declaration.color).toBe(property); + } + }); + }); + + describe('get floodColor()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + for (const property of ['inherit', 'red', 'rgb(255, 0, 0)', '#ff0000']) { + element.setAttribute('style', `flood-color: ${property}`); + expect(declaration.floodColor).toBe(property); + } }); }); From 1cea0737dec6da52c8481b65bb25b9d42e204375 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Thu, 22 Sep 2022 17:07:30 +0200 Subject: [PATCH 42/84] #344@trivial: Continue on CSSStyleDeclaration. --- .../CSSStyleDeclarationPropertyManager.ts | 32 ++++---- .../declaration/CSSStyleDeclaration.test.ts | 73 ++++++++++++++++++- 2 files changed, 85 insertions(+), 20 deletions(-) diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts index 7c0421ba8..092890157 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts @@ -119,40 +119,40 @@ export default class CSSStyleDeclarationPropertyManager { delete this.properties['border-image-outset']; delete this.properties['border-image-repeat']; break; - case 'border-left': - delete this.properties['border-left-width']; - delete this.properties['border-left-style']; - delete this.properties['border-left-color']; + case 'border-top': + delete this.properties['border-top-width']; + delete this.properties['border-top-style']; + delete this.properties['border-top-color']; delete this.properties['border-image-source']; delete this.properties['border-image-slice']; delete this.properties['border-image-width']; delete this.properties['border-image-outset']; delete this.properties['border-image-repeat']; break; - case 'border-bottom': - delete this.properties['border-bottom-width']; - delete this.properties['border-bottom-style']; - delete this.properties['border-bottom-color']; + case 'border-right': + delete this.properties['border-right-width']; + delete this.properties['border-right-style']; + delete this.properties['border-right-color']; delete this.properties['border-image-source']; delete this.properties['border-image-slice']; delete this.properties['border-image-width']; delete this.properties['border-image-outset']; delete this.properties['border-image-repeat']; break; - case 'border-right': - delete this.properties['border-right-width']; - delete this.properties['border-right-style']; - delete this.properties['border-right-color']; + case 'border-bottom': + delete this.properties['border-bottom-width']; + delete this.properties['border-bottom-style']; + delete this.properties['border-bottom-color']; delete this.properties['border-image-source']; delete this.properties['border-image-slice']; delete this.properties['border-image-width']; delete this.properties['border-image-outset']; delete this.properties['border-image-repeat']; break; - case 'border-top': - delete this.properties['border-top-width']; - delete this.properties['border-top-style']; - delete this.properties['border-top-color']; + case 'border-left': + delete this.properties['border-left-width']; + delete this.properties['border-left-style']; + delete this.properties['border-left-color']; delete this.properties['border-image-source']; delete this.properties['border-image-slice']; delete this.properties['border-image-width']; diff --git a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts index 7b8ea9e18..b370d76cc 100644 --- a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts +++ b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts @@ -1880,11 +1880,11 @@ describe('CSSStyleDeclaration', () => { element.setAttribute( 'style', - `border: 2px solid green; border-radius: 2px;font-size: 12px;` + `border: green 2px solid; border-radius: 2px;font-size: 12px;` ); expect(declaration.cssText).toBe( - 'border: 2px solid green; border-radius: 2px; font-size: 12px;' + 'border: green 2px solid; border-radius: 2px; font-size: 12px;' ); }); @@ -2033,10 +2033,75 @@ describe('CSSStyleDeclaration', () => { it('Removes a CSS property when using element.', () => { const declaration = new CSSStyleDeclaration(element); - element.setAttribute('style', `border: 2px solid green;border-radius: 2px;font-size: 12px;`); + element.setAttribute('style', `border: 2px solid blue; color: red;`); + declaration.removeProperty('border'); + expect(element.getAttribute('style')).toBe('color: red;'); + + element.setAttribute('style', `border-top: 2px solid blue; color: red;`); + declaration.removeProperty('border-top'); + expect(element.getAttribute('style')).toBe('color: red;'); + + element.setAttribute('style', `border-right: 2px solid blue; color: red;`); + declaration.removeProperty('border-right'); + expect(element.getAttribute('style')).toBe('color: red;'); + + element.setAttribute('style', `border-bottom: 2px solid blue; color: red;`); + declaration.removeProperty('border-bottom'); + expect(element.getAttribute('style')).toBe('color: red;'); + + element.setAttribute('style', `border-left: 2px solid blue; color: red;`); + declaration.removeProperty('border-left'); + expect(element.getAttribute('style')).toBe('color: red;'); + + element.setAttribute('style', `border-width: 2px; color: red;`); + declaration.removeProperty('border-width'); + expect(element.getAttribute('style')).toBe('color: red;'); + + element.setAttribute('style', `border-style: solid; color: red;`); + declaration.removeProperty('border-style'); + expect(element.getAttribute('style')).toBe('color: red;'); + + element.setAttribute('style', `border-color: blue; color: red;`); + declaration.removeProperty('border-color'); + expect(element.getAttribute('style')).toBe('color: red;'); + + element.setAttribute( + 'style', + `border-image: url('/media/examples/border-diamonds.png') 30; color: red;` + ); + declaration.removeProperty('border-image'); + expect(element.getAttribute('style')).toBe('color: red;'); + + element.setAttribute('style', `border-radius: 2px;color: red;`); declaration.removeProperty('border-radius'); + expect(element.getAttribute('style')).toBe('color: red;'); + + element.setAttribute( + 'style', + `background: no-repeat center/80% url("../img/image.png");color: red;` + ); + declaration.removeProperty('background'); + expect(element.getAttribute('style')).toBe('color: red;'); + + element.setAttribute('style', `background-position: 25% 75%;color: red;`); + declaration.removeProperty('background-position'); + expect(element.getAttribute('style')).toBe('color: red;'); + + element.setAttribute('style', `flex: 3 2 min-content;color: red;`); + declaration.removeProperty('flex'); + expect(element.getAttribute('style')).toBe('color: red;'); + + element.setAttribute('style', `font: 1.2em "Fira Sans", sans-serif;color: red;`); + declaration.removeProperty('font'); + expect(element.getAttribute('style')).toBe('color: red;'); + + element.setAttribute('style', `padding: 1px 2px 3px 4px;color: red;`); + declaration.removeProperty('padding'); + expect(element.getAttribute('style')).toBe('color: red;'); - expect(element.getAttribute('style')).toBe('border: 2px solid green; font-size: 12px;'); + element.setAttribute('style', `margin: 1px 2px 3px 4px;color: red;`); + declaration.removeProperty('margin'); + expect(element.getAttribute('style')).toBe('color: red;'); }); it('Removes a CSS property without element.', () => { From d1a0bac40da7948724c34277600326343e9987ae Mon Sep 17 00:00:00 2001 From: David Ortner Date: Wed, 28 Sep 2022 11:37:49 +0200 Subject: [PATCH 43/84] #344@major: Improves the support for CSSStyleDeclaration and window.getComputedStyle(). Improves support for HTMLElement.innerText. --- .../CSSStyleDeclarationPropertyManager.ts | 3 ++ .../CSSStyleDeclarationPropertySetParser.ts | 33 ++++++++++++++++ .../CSSStyleDeclarationValueParser.ts | 4 ++ .../src/nodes/html-element/HTMLElement.ts | 20 +++++++++- packages/happy-dom/test/css/CSSParser.test.ts | 34 +++++++++-------- .../happy-dom/test/css/data/CSSParserInput.ts | 1 + .../declaration/CSSStyleDeclaration.test.ts | 38 ++++++++++++++++++- .../nodes/html-element/HTMLElement.test.ts | 21 +++++++++- 8 files changed, 133 insertions(+), 21 deletions(-) diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts index 092890157..ee88c0ca3 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts @@ -475,6 +475,9 @@ export default class CSSStyleDeclarationPropertyManager { case 'flood-color': properties = CSSStyleDeclarationPropertySetParser.getFloodColor(value, important); break; + case 'text-transform': + properties = CSSStyleDeclarationPropertySetParser.getTextTransform(value, important); + break; default: const trimmedValue = value.trim(); if (trimmedValue) { diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts index cdb5058a0..36012c9a3 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts @@ -81,6 +81,14 @@ const DISPLAY = [ 'list-item' ]; const BORDER_IMAGE_REPEAT = ['stretch', 'repeat', 'round', 'space']; +const TEXT_TRANSFORM = [ + 'capitalize', + 'uppercase', + 'lowercase', + 'none', + 'full-width', + 'full-size-kana' +]; /** * Computed style property parser. @@ -2585,4 +2593,29 @@ export default class CSSStyleDeclarationPropertySetParser { } }; } + + /** + * Returns font family. + * + * @param value Value. + * @param important Important. + * @returns Property values + */ + public static getTextTransform( + value: string, + important: boolean + ): { + [key: string]: ICSSStyleDeclarationPropertyValue; + } { + const lowerValue = value.toLowerCase(); + const parsedValue = + CSSStyleDeclarationValueParser.getGlobal(lowerValue) || + (TEXT_TRANSFORM.includes(lowerValue) && lowerValue); + if (parsedValue) { + return { + 'text-transform': { value: parsedValue, important } + }; + } + return null; + } } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts index 4bf0e09a9..b07bcc522 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts @@ -8,6 +8,7 @@ const URL_REGEXP = /^url\(\s*([^)]*)\s*\)$/; const INTEGER_REGEXP = /^[0-9]+$/; const FLOAT_REGEXP = /^[0-9.]+$/; const CALC_REGEXP = /^calc\([^^)]+\)$/; +const CSS_VARIABLE_REGEXP = /^var\( *--[^)]+\)$/; const FIT_CONTENT_REGEXP = /^fit-content\([^^)]+\)$/; const GRADIENT_REGEXP = /^(repeating-linear|linear|radial|repeating-radial|conic|repeating-conic)-gradient\([^)]+\)$/; @@ -405,6 +406,9 @@ export default class CSSStyleDeclarationValueParser { * @returns Parsed value. */ public static getGlobal(value: string): string { + if (CSS_VARIABLE_REGEXP.test(value)) { + return value; + } const lowerValue = value.toLowerCase(); return GLOBALS.includes(lowerValue) ? lowerValue : null; } diff --git a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts index cac25a8f6..24eb63bab 100644 --- a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts +++ b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts @@ -72,11 +72,27 @@ export default class HTMLElement extends Element implements IHTMLElement { const display = computedStyle.display; if (element.tagName !== 'SCRIPT' && element.tagName !== 'STYLE' && display !== 'none') { - if (display === 'block' && result) { + const textTransform = computedStyle.textTransform; + + if ((display === 'block' || display === 'flex') && result) { result += '\n'; } - result += element.innerText; + let text = element.innerText; + + switch (textTransform) { + case 'uppercase': + text = text.toUpperCase(); + break; + case 'lowercase': + text = text.toLowerCase(); + break; + case 'capitalize': + text = text.replace(/(^|\s)\S/g, (l) => l.toUpperCase()); + break; + } + + result += text; } } else if (childNode.nodeType === NodeTypeEnum.textNode) { result += childNode.textContent.replace(/[\n\r]/, ''); diff --git a/packages/happy-dom/test/css/CSSParser.test.ts b/packages/happy-dom/test/css/CSSParser.test.ts index 87d740391..7d6a86bc0 100644 --- a/packages/happy-dom/test/css/CSSParser.test.ts +++ b/packages/happy-dom/test/css/CSSParser.test.ts @@ -26,9 +26,9 @@ describe('CSSParser', () => { expect((cssRules[0]).style[0]).toBe('display'); expect((cssRules[0]).style[1]).toBe('overflow'); expect((cssRules[0]).style[2]).toBe('width'); - expect((cssRules[0]).style['display']).toBe('flex'); - expect((cssRules[0]).style['overflow']).toBe('hidden'); - expect((cssRules[0]).style['width']).toBe('100%'); + expect((cssRules[0]).style.display).toBe('flex'); + expect((cssRules[0]).style.overflow).toBe('hidden'); + expect((cssRules[0]).style.width).toBe('100%'); expect((cssRules[0]).style.cssText).toBe( 'display: flex; overflow: hidden; width: 100%;' ); @@ -38,20 +38,22 @@ describe('CSSParser', () => { expect((cssRules[1]).parentStyleSheet).toBe(cssStyleSheet); expect((cssRules[1]).selectorText).toBe('.container'); expect((cssRules[1]).cssText).toBe( - '.container { flex-grow: 1; display: flex; flex-direction: column; overflow: hidden; }' + '.container { flex-grow: 1; display: flex; flex-direction: column; overflow: hidden; --css-variable: 1px; }' ); - expect((cssRules[1]).style.length).toBe(4); + expect((cssRules[1]).style.length).toBe(5); expect((cssRules[1]).style.parentRule).toBe(cssRules[1]); expect((cssRules[1]).style[0]).toBe('flex-grow'); expect((cssRules[1]).style[1]).toBe('display'); expect((cssRules[1]).style[2]).toBe('flex-direction'); expect((cssRules[1]).style[3]).toBe('overflow'); - expect((cssRules[1]).style['flexGrow']).toBe('1'); - expect((cssRules[1]).style['display']).toBe('flex'); - expect((cssRules[1]).style['flexDirection']).toBe('column'); - expect((cssRules[1]).style['overflow']).toBe('hidden'); + expect((cssRules[1]).style[4]).toBe('--css-variable'); + expect((cssRules[1]).style.flexGrow).toBe('1'); + expect((cssRules[1]).style.display).toBe('flex'); + expect((cssRules[1]).style.flexDirection).toBe('column'); + expect((cssRules[1]).style.overflow).toBe('hidden'); + expect((cssRules[1]).style.getPropertyValue('--css-variable')).toBe('1px'); expect((cssRules[1]).style.cssText).toBe( - 'flex-grow: 1; display: flex; flex-direction: column; overflow: hidden;' + 'flex-grow: 1; display: flex; flex-direction: column; overflow: hidden; --css-variable: 1px;' ); // CSSMediaRule @@ -72,8 +74,8 @@ describe('CSSParser', () => { expect(children1[0].style.parentRule).toBe(children1[0]); expect(children1[0].style[0]).toBe('height'); expect(children1[0].style[1]).toBe('animation'); - expect(children1[0].style['height']).toBe('0.5rem'); - expect(children1[0].style['animation']).toBe('keyframes2 2s linear infinite'); + expect(children1[0].style.height).toBe('0.5rem'); + expect(children1[0].style.animation).toBe('keyframes2 2s linear infinite'); expect(children1[0].cssText).toBe( '.container { height: 0.5rem; animation: keyframes2 2s linear infinite; }' ); @@ -93,14 +95,14 @@ describe('CSSParser', () => { expect(children2[0].style.length).toBe(1); expect(children2[0].style.parentRule).toBe(children2[0]); expect(children2[0].style[0]).toBe('transform'); - expect(children2[0].style['transform']).toBe('rotate(0deg)'); + expect(children2[0].style.transform).toBe('rotate(0deg)'); expect(children2[0].cssText).toBe('from { transform: rotate(0deg); }'); expect(children2[1].parentRule).toBe(cssRules[3]); expect(children2[1].parentStyleSheet).toBe(cssStyleSheet); expect(children2[1].keyText).toBe('to'); expect(children2[1].style.length).toBe(1); expect(children2[1].style[0]).toBe('transform'); - expect(children2[1].style['transform']).toBe('rotate(360deg)'); + expect(children2[1].style.transform).toBe('rotate(360deg)'); expect(children2[1].cssText).toBe('to { transform: rotate(360deg); }'); // CSSKeyframesRule @@ -118,14 +120,14 @@ describe('CSSParser', () => { expect(children3[0].style.length).toBe(1); expect(children3[0].style.parentRule).toBe(children3[0]); expect(children3[0].style[0]).toBe('transform'); - expect(children3[0].style['transform']).toBe('rotate(0deg)'); + expect(children3[0].style.transform).toBe('rotate(0deg)'); expect(children3[0].cssText).toBe('0% { transform: rotate(0deg); }'); expect(children3[1].parentRule).toBe(cssRules[4]); expect(children3[1].parentStyleSheet).toBe(cssStyleSheet); expect(children3[1].keyText).toBe('100%'); expect(children3[1].style.length).toBe(1); expect(children3[1].style[0]).toBe('transform'); - expect(children3[1].style['transform']).toBe('rotate(360deg)'); + expect(children3[1].style.transform).toBe('rotate(360deg)'); expect(children3[1].cssText).toBe('100% { transform: rotate(360deg); }'); }); }); diff --git a/packages/happy-dom/test/css/data/CSSParserInput.ts b/packages/happy-dom/test/css/data/CSSParserInput.ts index bed47c0ee..dd461ca15 100644 --- a/packages/happy-dom/test/css/data/CSSParserInput.ts +++ b/packages/happy-dom/test/css/data/CSSParserInput.ts @@ -10,6 +10,7 @@ export default ` display: flex; flex-direction: column; overflow: hidden; + --css-variable: 1px; } @media screen and (max-width: 36rem) { diff --git a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts index b370d76cc..37d371c3b 100644 --- a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts +++ b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts @@ -927,6 +927,7 @@ describe('CSSStyleDeclaration', () => { const declaration = new CSSStyleDeclaration(element); for (const value of [ + 'var(--test-variable)', 'inherit', 'initial', 'revert', @@ -1854,6 +1855,30 @@ describe('CSSStyleDeclaration', () => { }); }); + describe('get textTransform()', () => { + it('Returns style property.', () => { + const declaration = new CSSStyleDeclaration(element); + + for (const value of [ + 'var(--test-variable)', + 'inherit', + 'initial', + 'revert', + 'unset', + 'capitalize', + 'uppercase', + 'lowercase', + 'none', + 'full-width', + 'full-size-kana' + ]) { + element.setAttribute('style', `text-transform: ${value}`); + + expect(declaration.textTransform).toBe(value); + } + }); + }); + describe('get length()', () => { it('Returns length when of styles on element.', () => { const declaration = new CSSStyleDeclaration(element); @@ -1884,7 +1909,7 @@ describe('CSSStyleDeclaration', () => { ); expect(declaration.cssText).toBe( - 'border: green 2px solid; border-radius: 2px; font-size: 12px;' + 'border: 2px solid green; border-radius: 2px; font-size: 12px;' ); }); @@ -2027,6 +2052,17 @@ describe('CSSStyleDeclaration', () => { expect(element.getAttribute('style')).toBe(null); }); + + it('Can set a CSS variable.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', `border: 2px solid green;`); + + declaration.setProperty('--test-key', 'value'); + + expect(element.getAttribute('style')).toBe('border: 2px solid green; --test-key: value;'); + expect(declaration.getPropertyValue('--test-key')).toBe('value'); + }); }); describe('removeProperty()', () => { diff --git a/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts b/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts index c83258f88..06dc66de2 100644 --- a/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts +++ b/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts @@ -102,10 +102,27 @@ describe('HTMLElement', () => { expect(element.innerText).toBe('The quick brown foxJumped over the lazy dog'); }); - it('Returns rendered text with line breaks between block elements and without hidden elements being rendered if element is connected to the document.', () => { - element.innerHTML = `
The quick brown fox
Jumped over the lazy dog
`; + it('Returns rendered text with line breaks between block and flex elements and without hidden elements being rendered if element is connected to the document.', () => { document.body.appendChild(element); + + element.innerHTML = `
The quick brown fox
Jumped over the lazy dog
`; expect(element.innerText).toBe('The quick brown fox\nJumped over the lazy dog'); + + element.innerHTML = `
The quick brown fox
Jumped over the lazy dog
.
`; + expect(element.innerText).toBe('The quick brown fox\nJumped over the lazy dog\n.'); + }); + + it('Returns rendered text affected by the "text-transform" CSS property.', () => { + document.body.appendChild(element); + + element.innerHTML = `
The quick brown fox
Jumped over the lazy dog`; + expect(element.innerText).toBe('The quick brown fox\nJUMPED OVER THE LAZY DOG'); + + element.innerHTML = `
The quick brown fox
JUMPED OVER THE LAZY DOG`; + expect(element.innerText).toBe('The quick brown fox\njumped over the lazy dog'); + + element.innerHTML = `
The quick brown fox
jumped over the lazy dog`; + expect(element.innerText).toBe('The quick brown fox\nJumped Over The Lazy Dog'); }); }); From 36aceeb03728df27adae86e3f0d2664b3a85c6a9 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Wed, 28 Sep 2022 17:43:36 +0200 Subject: [PATCH 44/84] #344@trivial: Continue on CSSStyleDeclaration. --- .../AbstractCSSStyleDeclaration.ts | 14 ++++ .../utilities/CSSStyleDeclarationElement.ts | 17 +++- .../CSSStyleDeclarationPropertyManager.ts | 5 +- .../src/event/events/IMediaQueryListInit.ts | 6 ++ .../src/event/events/MediaQueryListEvent.ts | 25 ++++++ .../src/match-media/MediaQueryList.ts | 73 +++++++++++++++--- packages/happy-dom/src/window/IWindow.ts | 4 + packages/happy-dom/src/window/Window.ts | 39 ++++++++-- .../declaration/CSSStyleDeclaration.test.ts | 26 +++++++ .../test/match-media/MediaQueryList.test.ts | 77 +++++++++++++++++++ packages/happy-dom/test/window/Window.test.ts | 12 ++- 11 files changed, 274 insertions(+), 24 deletions(-) create mode 100644 packages/happy-dom/src/event/events/IMediaQueryListInit.ts create mode 100644 packages/happy-dom/src/event/events/MediaQueryListEvent.ts create mode 100644 packages/happy-dom/test/match-media/MediaQueryList.test.ts diff --git a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts index 842970918..ca586628d 100644 --- a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts +++ b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts @@ -184,4 +184,18 @@ export default abstract class AbstractCSSStyleDeclaration { } return this._style.get(name)?.value || ''; } + + /** + * Returns a property. + * + * @param name Property name in kebab case. + * @returns "important" if set to be important. + */ + public getPropertyPriority(name: string): string { + if (this._ownerElement) { + const style = CSSStyleDeclarationElement.getElementStyle(this._ownerElement, this._computed); + return style.get(name)?.important ? 'important' : ''; + } + return this._style.get(name)?.important ? 'important' : ''; + } } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts index bfd6c9652..2ae41c55b 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts @@ -98,7 +98,9 @@ export default class CSSStyleDeclarationElement { ); for (const name of Object.keys(properties)) { if (CSSStyleDeclarationElementInheritedProperties.includes(name)) { - inheritedProperties[name] = properties[name]; + if (!inheritedProperties[name]?.important || properties[name].important) { + inheritedProperties[name] = properties[name]; + } } } } @@ -107,14 +109,21 @@ export default class CSSStyleDeclarationElement { targetElement.cssText + (targetElement.element['_attributes']['style']?.value || '') ); - targetPropertyManager.properties = Object.assign( + const targetProperties = Object.assign( {}, CSSStyleDeclarationElementDefaultProperties.default, CSSStyleDeclarationElementDefaultProperties[targetElement.element.tagName], - inheritedProperties, - targetPropertyManager.properties + inheritedProperties ); + for (const name of Object.keys(targetPropertyManager.properties)) { + if (!targetProperties[name]?.important || targetPropertyManager.properties[name].important) { + targetProperties[name] = targetPropertyManager.properties[name]; + } + } + + targetPropertyManager.properties = targetProperties; + return targetPropertyManager; } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts index ee88c0ca3..6f974cfe3 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts @@ -31,7 +31,10 @@ export default class CSSStyleDeclarationPropertyManager { const important = trimmedValue.endsWith(' !important'); const valueWithoutImportant = trimmedValue.replace(' !important', ''); - if (valueWithoutImportant) { + if ( + valueWithoutImportant && + (important || !this.properties[trimmedName]?.important) + ) { this.set(trimmedName, valueWithoutImportant, important); } } diff --git a/packages/happy-dom/src/event/events/IMediaQueryListInit.ts b/packages/happy-dom/src/event/events/IMediaQueryListInit.ts new file mode 100644 index 000000000..0f202840a --- /dev/null +++ b/packages/happy-dom/src/event/events/IMediaQueryListInit.ts @@ -0,0 +1,6 @@ +import IEventInit from '../IEventInit'; + +export default interface IMediaQueryListInit extends IEventInit { + matches?: boolean; + media?: string; +} diff --git a/packages/happy-dom/src/event/events/MediaQueryListEvent.ts b/packages/happy-dom/src/event/events/MediaQueryListEvent.ts new file mode 100644 index 000000000..384269c33 --- /dev/null +++ b/packages/happy-dom/src/event/events/MediaQueryListEvent.ts @@ -0,0 +1,25 @@ +import Event from '../Event'; +import IMediaQueryListInit from './IMediaQueryListInit'; + +/** + * + */ +export default class MediaQueryListEvent extends Event { + public readonly matches: boolean = false; + public readonly media: string = ''; + + /** + * Constructor. + * + * @param type Event type. + * @param [eventInit] Event init. + */ + constructor(type: string, eventInit: IMediaQueryListInit = null) { + super(type, eventInit); + + if (eventInit) { + this.matches = eventInit.matches || false; + this.media = eventInit.media || ''; + } + } +} diff --git a/packages/happy-dom/src/match-media/MediaQueryList.ts b/packages/happy-dom/src/match-media/MediaQueryList.ts index 250aa3472..175a5394e 100644 --- a/packages/happy-dom/src/match-media/MediaQueryList.ts +++ b/packages/happy-dom/src/match-media/MediaQueryList.ts @@ -1,5 +1,11 @@ import EventTarget from '../event/EventTarget'; import Event from '../event/Event'; +import IWindow from '../window/IWindow'; +import IEventListener from '../event/IEventListener'; +import MediaQueryListEvent from '../event/events/MediaQueryListEvent'; + +const MEDIA_REGEXP = + /min-width: *([0-9]+) *px|max-width: *([0-9]+) *px|min-height: *([0-9]+) *px|max-height: *([0-9]+) *px/; /** * Media Query List. @@ -8,26 +14,41 @@ import Event from '../event/Event'; * https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList. */ export default class MediaQueryList extends EventTarget { - public _matches = false; - public _media = ''; + public readonly media: string = ''; public onchange: (event: Event) => void = null; + private _ownerWindow: IWindow; /** - * Returns "true" if the document matches. + * Constructor. * - * @returns Matches. + * @param ownerWindow Window. + * @param media Media. */ - public get matches(): boolean { - return this._matches; + constructor(ownerWindow: IWindow, media: string) { + super(); + this._ownerWindow = ownerWindow; + this.media = media; } /** - * Returns the serialized media query. + * Returns "true" if the document matches. * - * @returns Serialized media query. + * @returns Matches. */ - public get media(): string { - return this._media; + public get matches(): boolean { + const match = MEDIA_REGEXP.exec(this.media); + if (match) { + if (match[1]) { + return this._ownerWindow.innerWidth >= parseInt(match[1]); + } else if (match[2]) { + return this._ownerWindow.innerWidth <= parseInt(match[2]); + } else if (match[3]) { + return this._ownerWindow.innerHeight >= parseInt(match[3]); + } else if (match[4]) { + return this._ownerWindow.innerHeight <= parseInt(match[4]); + } + } + return false; } /** @@ -49,4 +70,36 @@ export default class MediaQueryList extends EventTarget { public removeListener(callback: (event: Event) => void): void { this.removeEventListener('change', callback); } + + /** + * @override + */ + public addEventListener(type: string, listener: IEventListener | ((event: Event) => void)): void { + super.addEventListener(type, listener); + if (type === 'change') { + let matchesState = false; + const resizeListener = (): void => { + const matches = this.matches; + if (matches !== matchesState) { + matchesState = matches; + this.dispatchEvent(new MediaQueryListEvent('change', { matches, media: this.media })); + } + }; + listener['_windowResizeListener'] = resizeListener; + this._ownerWindow.addEventListener('resize', resizeListener); + } + } + + /** + * @override + */ + public removeEventListener( + type: string, + listener: IEventListener | ((event: Event) => void) + ): void { + super.removeEventListener(type, listener); + if (type === 'change' && listener['_windowResizeListener']) { + this._ownerWindow.removeEventListener('resize', listener['_windowResizeListener']); + } + } } diff --git a/packages/happy-dom/src/window/IWindow.ts b/packages/happy-dom/src/window/IWindow.ts index 56cb8302e..358b065fb 100644 --- a/packages/happy-dom/src/window/IWindow.ts +++ b/packages/happy-dom/src/window/IWindow.ts @@ -35,6 +35,7 @@ import CustomEvent from '../event/events/CustomEvent'; import AnimationEvent from '../event/events/AnimationEvent'; import KeyboardEvent from '../event/events/KeyboardEvent'; import ProgressEvent from '../event/events/ProgressEvent'; +import MediaQueryListEvent from '../event/events/MediaQueryListEvent'; import EventTarget from '../event/EventTarget'; import URL from '../location/URL'; import Location from '../location/Location'; @@ -97,6 +98,8 @@ export default interface IWindow extends IEventTarget, NodeJS.Global { whenAsyncComplete: () => Promise; cancelAsync: () => void; asyncTaskManager: AsyncTaskManager; + setInnerWidth: (width: number) => void; + setInnerHeight: (height: number) => void; }; // Global classes @@ -147,6 +150,7 @@ export default interface IWindow extends IEventTarget, NodeJS.Global { readonly ErrorEvent: typeof ErrorEvent; readonly StorageEvent: typeof StorageEvent; readonly ProgressEvent: typeof ProgressEvent; + readonly MediaQueryListEvent: typeof MediaQueryListEvent; readonly EventTarget: typeof EventTarget; readonly DataTransfer: typeof DataTransfer; readonly DataTransferItem: typeof DataTransferItem; diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts index 414522362..6b8a7b6e3 100644 --- a/packages/happy-dom/src/window/Window.ts +++ b/packages/happy-dom/src/window/Window.ts @@ -35,6 +35,7 @@ import CustomEvent from '../event/events/CustomEvent'; import AnimationEvent from '../event/events/AnimationEvent'; import KeyboardEvent from '../event/events/KeyboardEvent'; import ProgressEvent from '../event/events/ProgressEvent'; +import MediaQueryListEvent from '../event/events/MediaQueryListEvent'; import EventTarget from '../event/EventTarget'; import URL from '../location/URL'; import Location from '../location/Location'; @@ -119,7 +120,19 @@ export default class Window extends EventTarget implements IWindow { cancelAsync: (): void => { this.happyDOM.asyncTaskManager.cancelAll(); }, - asyncTaskManager: new AsyncTaskManager() + asyncTaskManager: new AsyncTaskManager(), + setInnerWidth: (width: number): void => { + if (this.innerWidth !== width) { + (this.innerWidth) = width; + this.dispatchEvent(new Event('resize')); + } + }, + setInnerHeight: (height: number): void => { + if (this.innerHeight !== height) { + (this.innerHeight) = height; + this.dispatchEvent(new Event('resize')); + } + } }; // Global classes @@ -168,6 +181,7 @@ export default class Window extends EventTarget implements IWindow { public readonly ErrorEvent = ErrorEvent; public readonly StorageEvent = StorageEvent; public readonly ProgressEvent = ProgressEvent; + public readonly MediaQueryListEvent = MediaQueryListEvent; public readonly EventTarget = EventTarget; public readonly DataTransfer = DataTransfer; public readonly DataTransferItem = DataTransferItem; @@ -188,7 +202,6 @@ export default class Window extends EventTarget implements IWindow { public readonly URLSearchParams = URLSearchParams; public readonly HTMLCollection = HTMLCollection; public readonly NodeList = NodeList; - public readonly MediaQueryList = MediaQueryList; public readonly CSSUnitValue = CSSUnitValue; public readonly Selection = Selection; public readonly Navigator = Navigator; @@ -226,12 +239,12 @@ export default class Window extends EventTarget implements IWindow { public readonly window = this; public readonly globalThis = this; public readonly screen = new Screen(); - public readonly innerWidth = 1024; - public readonly innerHeight = 768; public readonly devicePixelRatio = 1; public readonly sessionStorage = new Storage(); public readonly localStorage = new Storage(); public readonly performance = PerfHooks.performance; + public readonly innerWidth: number; + public readonly innerHeight: number; // Node.js Globals public ArrayBuffer; @@ -304,10 +317,22 @@ export default class Window extends EventTarget implements IWindow { /** * Constructor. + * + * @param [options] Options. + * @param [options.innerWidth] Inner width. + * @param [options.innerHeight] Inner height. + * @param [options.url] URL. */ - constructor() { + constructor(options?: { innerWidth?: number; innerHeight?: number; url?: string }) { super(); + this.innerWidth = options?.innerWidth ? options.innerWidth : 0; + this.innerHeight = options?.innerHeight ? options.innerHeight : 0; + + if (options?.url) { + this.location.href = options.url; + } + this._setTimeout = ORIGINAL_SET_TIMEOUT; this._clearTimeout = ORIGINAL_CLEAR_TIMEOUT; this._setInterval = ORIGINAL_SET_INTERVAL; @@ -485,9 +510,7 @@ export default class Window extends EventTarget implements IWindow { * @returns A new MediaQueryList. */ public matchMedia(mediaQueryString: string): MediaQueryList { - const mediaQueryList = new MediaQueryList(); - mediaQueryList._media = mediaQueryString; - return mediaQueryList; + return new MediaQueryList(this, mediaQueryString); } /** diff --git a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts index 37d371c3b..7fc60ca9a 100644 --- a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts +++ b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts @@ -2183,5 +2183,31 @@ describe('CSSStyleDeclaration', () => { expect(declaration.getPropertyValue('font-size')).toBe('12px'); expect(declaration.getPropertyValue('background')).toBe(''); }); + + it('Does not override important values when defined multiple times.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute( + 'style', + `text-transform: uppercase !important; text-transform: capitalize;` + ); + + expect(declaration.getPropertyValue('text-transform')).toBe('uppercase'); + expect(declaration.getPropertyPriority('text-transform')).toBe('important'); + }); + }); + + describe('getPropertyPriority()', () => { + it('Returns property priority.', () => { + const declaration = new CSSStyleDeclaration(element); + + element.setAttribute('style', `text-transform: uppercase`); + + expect(declaration.getPropertyPriority('text-transform')).toBe(''); + + element.setAttribute('style', `text-transform: uppercase !important`); + + expect(declaration.getPropertyPriority('text-transform')).toBe('important'); + }); }); }); diff --git a/packages/happy-dom/test/match-media/MediaQueryList.test.ts b/packages/happy-dom/test/match-media/MediaQueryList.test.ts new file mode 100644 index 000000000..c9b6cb3bf --- /dev/null +++ b/packages/happy-dom/test/match-media/MediaQueryList.test.ts @@ -0,0 +1,77 @@ +import IWindow from '../../src/window/IWindow'; +import Window from '../../src/window/Window'; +import MediaQueryList from '../../src/match-media/MediaQueryList'; +import MediaQueryListEvent from '../../src/event/events/MediaQueryListEvent'; + +describe('MediaQueryList', () => { + let window: IWindow; + + beforeEach(() => { + window = new Window({ innerWidth: 1024, innerHeight: 1024 }); + }); + + describe('get matches()', () => { + it('Handles "min-width".', () => { + expect(new MediaQueryList(window, '(min-width: 1025px)').matches).toBe(false); + expect(new MediaQueryList(window, '(min-width: 1024px)').matches).toBe(true); + }); + + it('Handles "max-width".', () => { + expect(new MediaQueryList(window, '(max-width: 1023px)').matches).toBe(false); + expect(new MediaQueryList(window, '(max-width: 1024px)').matches).toBe(true); + }); + + it('Handles "min-height".', () => { + expect(new MediaQueryList(window, '(min-height: 1025px)').matches).toBe(false); + expect(new MediaQueryList(window, '(min-height: 1024px)').matches).toBe(true); + }); + + it('Handles "max-height".', () => { + expect(new MediaQueryList(window, '(max-height: 1023px)').matches).toBe(false); + expect(new MediaQueryList(window, '(max-height: 1024px)').matches).toBe(true); + }); + }); + + describe('get media()', () => { + it('Returns media string.', () => { + const media = '(min-width: 1023px)'; + expect(new MediaQueryList(window, media).media).toBe(media); + }); + }); + + describe('addEventListener()', () => { + it('Listens for window "resize" event when sending in a "change" event.', () => { + let triggeredEvent = null; + const media = '(min-width: 1025px)'; + const mediaQueryList = new MediaQueryList(window, media); + + mediaQueryList.addEventListener('change', (event: MediaQueryListEvent): void => { + triggeredEvent = event; + }); + + expect(mediaQueryList.matches).toBe(false); + + window.happyDOM.setInnerWidth(1025); + + expect(triggeredEvent.matches).toBe(true); + expect(triggeredEvent.media).toBe(media); + }); + }); + + describe('removeEventListener()', () => { + it('Removes listener for window "resize" event when sending in a "change" event.', () => { + let triggeredEvent = null; + const mediaQueryList = new MediaQueryList(window, '(min-width: 1025px)'); + const listener = (event: MediaQueryListEvent): void => { + triggeredEvent = event; + }; + + mediaQueryList.addEventListener('change', listener); + mediaQueryList.removeEventListener('change', listener); + + window.happyDOM.setInnerWidth(1025); + + expect(triggeredEvent).toBe(null); + }); + }); +}); diff --git a/packages/happy-dom/test/window/Window.test.ts b/packages/happy-dom/test/window/Window.test.ts index 73d701249..20567397d 100644 --- a/packages/happy-dom/test/window/Window.test.ts +++ b/packages/happy-dom/test/window/Window.test.ts @@ -215,6 +215,10 @@ describe('Window', () => { window.document.body.appendChild(element); expect(computedStyle.color).toBe('red'); + + element.style.color = 'green'; + + expect(computedStyle.color).toBe('green'); }); it('Returns a CSSStyleDeclaration object with computed styles from style sheets.', () => { @@ -235,6 +239,7 @@ describe('Window', () => { document.body.appendChild(parent); expect(computedStyle.font).toBe('12px / 1.5 "Helvetica Neue", Helvetica, Arial, sans-serif'); + expect(computedStyle.border).toBe('1px solid #000'); }); }); @@ -301,11 +306,16 @@ describe('Window', () => { describe('matchMedia()', () => { it('Returns a new MediaQueryList object that can then be used to determine if the document matches the media query string.', () => { - const mediaQueryString = '(max-width: 600px)'; + window.happyDOM.setInnerWidth(1024); + + const mediaQueryString = '(max-width: 512px)'; const mediaQueryList = window.matchMedia(mediaQueryString); expect(mediaQueryList.matches).toBe(false); expect(mediaQueryList.media).toBe(mediaQueryString); expect(mediaQueryList.onchange).toBe(null); + + expect(window.matchMedia('(max-width: 1024px)').matches).toBe(true); + expect(typeof mediaQueryList.addEventListener).toBe('function'); expect(typeof mediaQueryList.removeEventListener).toBe('function'); }); From 606cff83e6d358c41954781fbd55673b6cbd70e7 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Thu, 29 Sep 2022 00:33:58 +0200 Subject: [PATCH 45/84] #344@trivial: Continues on CSSStyleDeclaration. --- .../utilities/CSSStyleDeclarationElement.ts | 114 ++++++++++-------- .../CSSStyleDeclarationPropertyManager.ts | 5 +- packages/happy-dom/test/CustomElement.ts | 21 +--- packages/happy-dom/test/window/Window.test.ts | 52 ++++++-- .../test/xml-serializer/XMLSerializer.test.ts | 15 +-- 5 files changed, 120 insertions(+), 87 deletions(-) diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts index 2ae41c55b..93271e308 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts @@ -42,64 +42,70 @@ export default class CSSStyleDeclarationElement { * @returns Style sheets. */ private static getComputedElementStyle(element: IElement): CSSStyleDeclarationPropertyManager { - const targetElement: { element: IElement; cssText: string } = { element, cssText: '' }; const parentElements: Array<{ element: IElement; cssText: string }> = []; const inheritedProperties: { [k: string]: ICSSStyleDeclarationPropertyValue } = {}; - let shadowRootElements: Array<{ element: IElement; cssText: string }> = [targetElement]; - let currentNode: IElement | IShadowRoot | IDocument = ( - element.parentNode - ); + let styleAndElement = { element: element, cssText: '' }; + let shadowRootElements: Array<{ element: IElement; cssText: string }> = []; if (!element.isConnected) { return new CSSStyleDeclarationPropertyManager(); } - while (currentNode) { - const styleAndElement = { element: currentNode, cssText: '' }; - - if (currentNode.nodeType === NodeTypeEnum.elementNode) { - parentElements.unshift(styleAndElement); - shadowRootElements.unshift(styleAndElement); + while (styleAndElement.element) { + if (styleAndElement.element.nodeType === NodeTypeEnum.elementNode) { + parentElements.unshift(<{ element: IElement; cssText: string }>styleAndElement); + shadowRootElements.unshift(<{ element: IElement; cssText: string }>styleAndElement); } - currentNode = currentNode.parentNode; + if (styleAndElement.element === element.ownerDocument) { + const styleSheets = >( + element.ownerDocument.querySelectorAll('style') + ); - if (currentNode) { - if (currentNode === element.ownerDocument) { - const styleSheets = >( - element.ownerDocument.querySelectorAll('style') - ); - for (const styleSheet of styleSheets) { - this.applyCSSTextToElements(shadowRootElements, styleSheet.sheet.cssRules); - } - currentNode = null; - } else if ((currentNode).host) { - const styleSheets = >( - (currentNode).querySelectorAll('style') + for (const styleSheet of styleSheets) { + this.applyCSSTextToElements(parentElements, styleSheet.sheet.cssRules); + } + + styleAndElement = { element: null, cssText: '' }; + } else if ((styleAndElement.element).host) { + const styleSheets = >( + (styleAndElement.element).querySelectorAll('style') + ); + styleAndElement = { + element: (styleAndElement.element).host, + cssText: '' + }; + for (const styleSheet of styleSheets) { + this.applyCSSTextToElements( + shadowRootElements, + styleSheet.sheet.cssRules, + <{ element: IElement; cssText: string }>styleAndElement ); - for (const styleSheet of styleSheets) { - this.applyCSSTextToElements(shadowRootElements, styleSheet.sheet.cssRules); - } - currentNode = (currentNode).host; - shadowRootElements = []; } + shadowRootElements = []; + } else { + styleAndElement = { element: styleAndElement.element.parentNode, cssText: '' }; } } + const targetElement = parentElements[parentElements.length - 1]; + for (const parentElement of parentElements) { - const propertyManager = new CSSStyleDeclarationPropertyManager( - parentElement.cssText + (parentElement.element['_attributes']['style']?.value || '') - ); - const properties = Object.assign( - {}, - CSSStyleDeclarationElementDefaultProperties.default, - CSSStyleDeclarationElementDefaultProperties[parentElement.element.tagName], - propertyManager.properties - ); - for (const name of Object.keys(properties)) { - if (CSSStyleDeclarationElementInheritedProperties.includes(name)) { - if (!inheritedProperties[name]?.important || properties[name].important) { - inheritedProperties[name] = properties[name]; + if (parentElement !== targetElement) { + const propertyManager = new CSSStyleDeclarationPropertyManager( + parentElement.cssText + (parentElement.element['_attributes']['style']?.value || '') + ); + const properties = Object.assign( + {}, + CSSStyleDeclarationElementDefaultProperties.default, + CSSStyleDeclarationElementDefaultProperties[parentElement.element.tagName], + propertyManager.properties + ); + for (const name of Object.keys(properties)) { + if (CSSStyleDeclarationElementInheritedProperties.includes(name)) { + if (!inheritedProperties[name]?.important || properties[name].important) { + inheritedProperties[name] = properties[name]; + } } } } @@ -132,10 +138,14 @@ export default class CSSStyleDeclarationElement { * * @param elements Elements. * @param cssRules CSS rules. + * @param [hostElement] Host element. + * @param [hostElement.element] Element. + * @param [hostElement.cssText] CSS text. */ private static applyCSSTextToElements( elements: Array<{ element: IElement; cssText: string }>, - cssRules: CSSRule[] + cssRules: CSSRule[], + hostElement?: { element: IElement; cssText: string } ): void { if (!elements.length) { return; @@ -146,12 +156,18 @@ export default class CSSStyleDeclarationElement { for (const rule of cssRules) { if (rule.type === CSSRuleTypeEnum.styleRule) { for (const element of elements) { - const selectorText = (rule).selectorText; - - if (selectorText && element.element.matches(selectorText)) { - const firstBracket = rule.cssText.indexOf('{'); - const lastBracket = rule.cssText.lastIndexOf('}'); - element.cssText += rule.cssText.substring(firstBracket + 1, lastBracket); + const selectorText: string = (rule).selectorText; + + if (selectorText) { + if (hostElement && selectorText.startsWith(':host')) { + const firstBracket = rule.cssText.indexOf('{'); + const lastBracket = rule.cssText.lastIndexOf('}'); + hostElement.cssText += rule.cssText.substring(firstBracket + 1, lastBracket); + } else if (element.element.matches(selectorText)) { + const firstBracket = rule.cssText.indexOf('{'); + const lastBracket = rule.cssText.lastIndexOf('}'); + element.cssText += rule.cssText.substring(firstBracket + 1, lastBracket); + } } } } else if ( diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts index 6f974cfe3..5768d70f4 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts @@ -31,10 +31,7 @@ export default class CSSStyleDeclarationPropertyManager { const important = trimmedValue.endsWith(' !important'); const valueWithoutImportant = trimmedValue.replace(' !important', ''); - if ( - valueWithoutImportant && - (important || !this.properties[trimmedName]?.important) - ) { + if (valueWithoutImportant && (important || !this.get(trimmedName)?.important)) { this.set(trimmedName, valueWithoutImportant, important); } } diff --git a/packages/happy-dom/test/CustomElement.ts b/packages/happy-dom/test/CustomElement.ts index db3b3b4dd..9ae0feb99 100644 --- a/packages/happy-dom/test/CustomElement.ts +++ b/packages/happy-dom/test/CustomElement.ts @@ -44,24 +44,15 @@ export default class CustomElement extends new Window().HTMLElement { :host { display: block; } - div { - color: red; - } - .class1 { - color: blue; - } - .class1.class2 span { - color: green; - } - .class1[attr1="value1"] { - color: yellow; - } - [attr1="value1"] { - color: yellow; + span { + color: pink; } + .class1 { + color: yellow; + }
- + key1 is "${this.getAttribute('key1')}" and key2 is "${this.getAttribute( 'key2' )}". diff --git a/packages/happy-dom/test/window/Window.test.ts b/packages/happy-dom/test/window/Window.test.ts index 20567397d..1fc466ac3 100644 --- a/packages/happy-dom/test/window/Window.test.ts +++ b/packages/happy-dom/test/window/Window.test.ts @@ -11,6 +11,7 @@ import Headers from '../../src/fetch/Headers'; import Selection from '../../src/selection/Selection'; import DOMException from '../../src/exception/DOMException'; import DOMExceptionNameEnum from '../../src/exception/DOMExceptionNameEnum'; +import CustomElement from '../../test/CustomElement'; const MOCKED_NODE_FETCH = global['mockedModules']['node-fetch']; @@ -24,6 +25,7 @@ describe('Window', () => { MOCKED_NODE_FETCH.error = null; window = new Window(); document = window.document; + window.customElements.define('custom-element', CustomElement); }); afterEach(() => { @@ -223,23 +225,59 @@ describe('Window', () => { it('Returns a CSSStyleDeclaration object with computed styles from style sheets.', () => { const parent = document.createElement('div'); - const element = document.createElement('div'); + const element = document.createElement('span'); const computedStyle = window.getComputedStyle(element); - const documentStyle = document.createElement('style'); + const parentStyle = document.createElement('style'); const elementStyle = document.createElement('style'); + const customElement = document.createElement('custom-element'); + + window.happyDOM.setInnerWidth(1024); + + parentStyle.innerHTML = ` + div { + font: 12px/1.5 "Helvetica Neue", Helvetica, Arial,sans-serif; + color: red !important; + cursor: pointer; + } + + span { + border-radius: 1px !important; + } - documentStyle.innerHTML = - 'div { font: 12px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; }'; - elementStyle.innerHTML = 'div { border: 1px solid #000; }'; + @media (min-width: 1024px) { + div { + font-size: 14px; + } + } + + @media (max-width: 768px) { + div { + font-size: 20px; + } + } + `; + + elementStyle.innerHTML = ` + span { + border: 1px solid #000; + border-radius: 2px; + color: green; + cursor: default; + } + `; parent.appendChild(elementStyle); parent.appendChild(element); - document.body.appendChild(documentStyle); + document.body.appendChild(parentStyle); document.body.appendChild(parent); + document.body.appendChild(customElement); - expect(computedStyle.font).toBe('12px / 1.5 "Helvetica Neue", Helvetica, Arial, sans-serif'); + expect(computedStyle.font).toBe('14px / 1.5 "Helvetica Neue", Helvetica, Arial, sans-serif'); expect(computedStyle.border).toBe('1px solid #000'); + expect(computedStyle.borderRadius).toBe('1px'); + expect(computedStyle.color).toBe('red'); + expect(computedStyle.cursor).toBe('default'); }); }); diff --git a/packages/happy-dom/test/xml-serializer/XMLSerializer.test.ts b/packages/happy-dom/test/xml-serializer/XMLSerializer.test.ts index fe12593b8..bf7a67635 100644 --- a/packages/happy-dom/test/xml-serializer/XMLSerializer.test.ts +++ b/packages/happy-dom/test/xml-serializer/XMLSerializer.test.ts @@ -162,24 +162,15 @@ describe('XMLSerializer', () => { :host { display: block; } - div { - color: red; + span { + color: pink; } .class1 { - color: blue; - } - .class1.class2 span { - color: green; - } - .class1[attr1="value1"] { - color: yellow; - } - [attr1="value1"] { color: yellow; }
- + key1 is "value1" and key2 is "value2". From 2e3bfa59efb475d2db4dd19516311a867a7e4e91 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Thu, 29 Sep 2022 16:25:05 +0200 Subject: [PATCH 46/84] #344@trivial: Continue on CSSStyleDeclaration. --- .../utilities/CSSStyleDeclarationElement.ts | 44 ++++++++++++------- packages/happy-dom/src/window/IWindow.ts | 3 +- packages/happy-dom/test/CustomElement.ts | 1 + packages/happy-dom/test/window/Window.test.ts | 31 ++++++++++++- .../test/xml-serializer/XMLSerializer.test.ts | 1 + 5 files changed, 62 insertions(+), 18 deletions(-) diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts index 93271e308..c08e1b5c8 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts @@ -42,6 +42,7 @@ export default class CSSStyleDeclarationElement { * @returns Style sheets. */ private static getComputedElementStyle(element: IElement): CSSStyleDeclarationPropertyManager { + const documentElements: Array<{ element: IElement; cssText: string }> = []; const parentElements: Array<{ element: IElement; cssText: string }> = []; const inheritedProperties: { [k: string]: ICSSStyleDeclarationPropertyValue } = {}; let styleAndElement = { element: element, cssText: '' }; @@ -51,10 +52,16 @@ export default class CSSStyleDeclarationElement { return new CSSStyleDeclarationPropertyManager(); } + // Walks through all parent elements and applies style to them. while (styleAndElement.element) { if (styleAndElement.element.nodeType === NodeTypeEnum.elementNode) { + const rootNode = styleAndElement.element.getRootNode(); + if (rootNode.nodeType === NodeTypeEnum.documentNode) { + documentElements.unshift(<{ element: IElement; cssText: string }>styleAndElement); + } else { + shadowRootElements.unshift(<{ element: IElement; cssText: string }>styleAndElement); + } parentElements.unshift(<{ element: IElement; cssText: string }>styleAndElement); - shadowRootElements.unshift(<{ element: IElement; cssText: string }>styleAndElement); } if (styleAndElement.element === element.ownerDocument) { @@ -63,7 +70,7 @@ export default class CSSStyleDeclarationElement { ); for (const styleSheet of styleSheets) { - this.applyCSSTextToElements(parentElements, styleSheet.sheet.cssRules); + this.applyCSSTextToElements(documentElements, styleSheet.sheet.cssRules); } styleAndElement = { element: null, cssText: '' }; @@ -90,6 +97,7 @@ export default class CSSStyleDeclarationElement { const targetElement = parentElements[parentElements.length - 1]; + // Walks through all parent elements and merges inherited properties. for (const parentElement of parentElements) { if (parentElement !== targetElement) { const propertyManager = new CSSStyleDeclarationPropertyManager( @@ -111,6 +119,7 @@ export default class CSSStyleDeclarationElement { } } + // Merges together styles in the target element with inherited properties. const targetPropertyManager = new CSSStyleDeclarationPropertyManager( targetElement.cssText + (targetElement.element['_attributes']['style']?.value || '') ); @@ -155,18 +164,23 @@ export default class CSSStyleDeclarationElement { for (const rule of cssRules) { if (rule.type === CSSRuleTypeEnum.styleRule) { - for (const element of elements) { - const selectorText: string = (rule).selectorText; - - if (selectorText) { - if (hostElement && selectorText.startsWith(':host')) { - const firstBracket = rule.cssText.indexOf('{'); - const lastBracket = rule.cssText.lastIndexOf('}'); - hostElement.cssText += rule.cssText.substring(firstBracket + 1, lastBracket); - } else if (element.element.matches(selectorText)) { - const firstBracket = rule.cssText.indexOf('{'); - const lastBracket = rule.cssText.lastIndexOf('}'); - element.cssText += rule.cssText.substring(firstBracket + 1, lastBracket); + const selectorText: string = (rule).selectorText; + if (selectorText) { + if (selectorText.startsWith(':host')) { + if (hostElement) { + const cssText = rule.cssText; + const firstBracket = cssText.indexOf('{'); + const lastBracket = cssText.lastIndexOf('}'); + hostElement.cssText += cssText.substring(firstBracket + 1, lastBracket); + } + } else { + for (const element of elements) { + if (element.element.matches(selectorText)) { + const cssText = rule.cssText; + const firstBracket = cssText.indexOf('{'); + const lastBracket = cssText.lastIndexOf('}'); + element.cssText += cssText.substring(firstBracket + 1, lastBracket); + } } } } @@ -174,7 +188,7 @@ export default class CSSStyleDeclarationElement { rule.type === CSSRuleTypeEnum.mediaRule && defaultView.matchMedia((rule).conditionalText).matches ) { - this.applyCSSTextToElements(elements, (rule).cssRules); + this.applyCSSTextToElements(elements, (rule).cssRules, hostElement); } } } diff --git a/packages/happy-dom/src/window/IWindow.ts b/packages/happy-dom/src/window/IWindow.ts index 358b065fb..c858acbae 100644 --- a/packages/happy-dom/src/window/IWindow.ts +++ b/packages/happy-dom/src/window/IWindow.ts @@ -88,6 +88,7 @@ import Window from './Window'; import Attr from '../nodes/attr/Attr'; import { URLSearchParams } from 'url'; import { Performance } from 'perf_hooks'; +import IElement from '../nodes/element/IElement'; /** * Window without dependencies to server side specific packages. @@ -223,7 +224,7 @@ export default interface IWindow extends IEventTarget, NodeJS.Global { * @param element Element. * @returns CSS style declaration. */ - getComputedStyle(element: IHTMLElement): CSSStyleDeclaration; + getComputedStyle(element: IElement): CSSStyleDeclaration; /** * Returns selection. diff --git a/packages/happy-dom/test/CustomElement.ts b/packages/happy-dom/test/CustomElement.ts index 9ae0feb99..f4c5b7b70 100644 --- a/packages/happy-dom/test/CustomElement.ts +++ b/packages/happy-dom/test/CustomElement.ts @@ -43,6 +43,7 @@ export default class CustomElement extends new Window().HTMLElement {