diff --git a/packages/happy-dom/src/nodes/node/Node.ts b/packages/happy-dom/src/nodes/node/Node.ts index 686a0713d..1e5e8a733 100644 --- a/packages/happy-dom/src/nodes/node/Node.ts +++ b/packages/happy-dom/src/nodes/node/Node.ts @@ -1025,7 +1025,10 @@ export default class Node extends EventTarget { */ public [PropertySymbol.disconnectedFromDocument](): void { this[PropertySymbol.isConnected] = false; - this[PropertySymbol.rootNode] = null; + + if (this[PropertySymbol.nodeType] !== NodeTypeEnum.documentFragmentNode) { + this[PropertySymbol.rootNode] = null; + } if (this[PropertySymbol.ownerDocument][PropertySymbol.activeElement] === this) { this[PropertySymbol.ownerDocument][PropertySymbol.clearCache](); diff --git a/packages/happy-dom/src/nodes/shadow-root/ShadowRoot.ts b/packages/happy-dom/src/nodes/shadow-root/ShadowRoot.ts index eabfc8046..2723a908d 100644 --- a/packages/happy-dom/src/nodes/shadow-root/ShadowRoot.ts +++ b/packages/happy-dom/src/nodes/shadow-root/ShadowRoot.ts @@ -7,6 +7,7 @@ import CSSStyleSheet from '../../css/CSSStyleSheet.js'; import HTMLElement from '../../nodes/html-element/HTMLElement.js'; import Event from '../../event/Event.js'; import SVGElement from '../svg-element/SVGElement.js'; +import Document from '../document/Document.js'; /** * ShadowRoot. @@ -165,16 +166,25 @@ export default class ShadowRoot extends DocumentFragment { * @returns Active element. */ public get activeElement(): HTMLElement | SVGElement | null { - const activeElement: HTMLElement | SVGElement = + let activeElement: HTMLElement | SVGElement = this[PropertySymbol.ownerDocument][PropertySymbol.activeElement]; - if ( - activeElement && - activeElement[PropertySymbol.isConnected] && - activeElement.getRootNode() === this - ) { + + let rootNode: ShadowRoot | Document = activeElement?.getRootNode(); + + if (!rootNode || rootNode === this[PropertySymbol.ownerDocument]) { + return null; + } + + if (rootNode === this) { return activeElement; } - return null; + + while (rootNode && rootNode !== this) { + activeElement = (rootNode).host; + rootNode = activeElement.getRootNode(); + } + + return activeElement; } /** diff --git a/packages/happy-dom/test/nodes/node/Node.test.ts b/packages/happy-dom/test/nodes/node/Node.test.ts index 0645eb570..d18145aea 100644 --- a/packages/happy-dom/test/nodes/node/Node.test.ts +++ b/packages/happy-dom/test/nodes/node/Node.test.ts @@ -366,6 +366,14 @@ describe('Node', () => { const rootNode = (customElement.shadowRoot).querySelector('span')?.getRootNode(); expect(rootNode === customElement.shadowRoot).toBe(true); + + document.body.removeChild(customElement); + + document.body.appendChild(customElement); + + const rootNode2 = (customElement.shadowRoot).querySelector('span')?.getRootNode(); + + expect(rootNode2 === customElement.shadowRoot).toBe(true); }); it('Returns Document when used on a node inside a ShadowRoot and the option "composed" is set to "true".', () => { diff --git a/packages/happy-dom/test/nodes/shadow-root/ShadowRoot.test.ts b/packages/happy-dom/test/nodes/shadow-root/ShadowRoot.test.ts index 026b0483d..b772fea67 100644 --- a/packages/happy-dom/test/nodes/shadow-root/ShadowRoot.test.ts +++ b/packages/happy-dom/test/nodes/shadow-root/ShadowRoot.test.ts @@ -218,6 +218,29 @@ describe('ShadowRoot', () => { expect(shadowRoot.activeElement === null).toBe(true); }); + + it('Returns the first custom element when the active element is not a child of the ShadowRoot, but is a child of a custom element within it.', () => { + const customElement = document.createElement('custom-element'); + const shadowRoot = customElement.shadowRoot; + const div = document.createElement('div'); + const customElementA = document.createElement('custom-element-a'); + const shadowRootA = customElementA.shadowRoot; + + document.body.appendChild(customElement); + + shadowRoot.appendChild(customElementA); + shadowRootA.appendChild(div); + + expect(shadowRoot.activeElement === null).toBe(true); + + div.focus(); + + expect(shadowRoot.activeElement).toBe(customElementA); + + customElementA.remove(); + + expect(shadowRoot.activeElement === null).toBe(true); + }); }); describe('getAnimations()', () => {