Skip to content

Commit

Permalink
fix: [#1722] The slotchange event should be fired after the element h…
Browse files Browse the repository at this point in the history
…as been connected to the DOM
  • Loading branch information
capricorn86 committed Feb 11, 2025
1 parent 70eb289 commit e93af7a
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import HTMLElement from '../nodes/html-element/HTMLElement.js';
import BrowserWindow from '../window/BrowserWindow.js';
import * as PropertySymbol from '../PropertySymbol.js';
import WindowBrowserContext from '../window/WindowBrowserContext.js';
import Element from '../nodes/element/Element.js';

/**
* Custom element reaction stack.
Expand Down Expand Up @@ -29,7 +29,7 @@ export default class CustomElementReactionStack {
* @param callbackName Callback name.
* @param [args] Arguments.
*/
public enqueueReaction(element: HTMLElement, callbackName: string, args?: any[]): void {
public enqueueReaction(element: Element, callbackName: string, args?: any[]): void {
// If a polyfill is used, [PropertySymbol.registry] may be undefined
const definition = this.window.customElements[PropertySymbol.registry]?.get(element.localName);

Expand Down
37 changes: 36 additions & 1 deletion packages/happy-dom/src/nodes/element/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,25 @@ export default class Element
return null;
}

/**
* Connected callback.
*/
public connectedCallback?(): void;

/**
* Disconnected callback.
*/
public disconnectedCallback?(): void;

/**
* Attribute changed callback.
*
* @param name Name.
* @param oldValue Old value.
* @param newValue New value.
*/
public attributeChangedCallback?(name: string, oldValue: string, newValue: string): void;

/**
* Query CSS selector to find matching nodes.
*
Expand Down Expand Up @@ -1449,6 +1468,17 @@ export default class Element
}

super[PropertySymbol.connectedToDocument]();

this[PropertySymbol.window][PropertySymbol.customElementReactionStack].enqueueReaction(
this,
'connectedCallback'
);

if (this[PropertySymbol.shadowRoot]) {
for (const childNode of this[PropertySymbol.nodeArray]) {
this.#onSlotChange(childNode);
}
}
}

/**
Expand All @@ -1461,6 +1491,11 @@ export default class Element
if (id) {
this.#removeIdentifierFromWindow(id);
}

this[PropertySymbol.window][PropertySymbol.customElementReactionStack].enqueueReaction(
this,
'disconnectedCallback'
);
}

/**
Expand Down Expand Up @@ -1563,7 +1598,7 @@ export default class Element
#onSlotChange(addedOrRemovedNode: Node): void {
const shadowRoot = this[PropertySymbol.shadowRoot];

if (!shadowRoot) {
if (!shadowRoot || !this[PropertySymbol.isConnected]) {
return;
}

Expand Down
43 changes: 0 additions & 43 deletions packages/happy-dom/src/nodes/html-element/HTMLElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -498,25 +498,6 @@ export default class HTMLElement extends Element {
this.setAttribute('popover', value);
}

/**
* Connected callback.
*/
public connectedCallback?(): void;

/**
* Disconnected callback.
*/
public disconnectedCallback?(): void;

/**
* Attribute changed callback.
*
* @param name Name.
* @param oldValue Old value.
* @param newValue New value.
*/
public attributeChangedCallback?(name: string, oldValue: string, newValue: string): void;

/**
* Triggers a click event.
*/
Expand Down Expand Up @@ -612,30 +593,6 @@ export default class HTMLElement extends Element {
super[PropertySymbol.disconnectedFromNode]();
}

/**
* @override
*/
public override [PropertySymbol.connectedToDocument](): void {
super[PropertySymbol.connectedToDocument]();

this[PropertySymbol.window][PropertySymbol.customElementReactionStack].enqueueReaction(
this,
'connectedCallback'
);
}

/**
* @override
*/
public override [PropertySymbol.disconnectedFromDocument](): void {
super[PropertySymbol.disconnectedFromDocument]();

this[PropertySymbol.window][PropertySymbol.customElementReactionStack].enqueueReaction(
this,
'disconnectedCallback'
);
}

/**
* @override
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -462,5 +462,43 @@ describe('HTMLSlotElement', () => {
expect(dispatchedEvent2).toBe(null);
expect(dispatchedEvent3).toBe(null);
});

it('Fires slotchange after the element is connected to the document', () => {
const lifecycle: string[] = [];
/* eslint-disable jsdoc/require-jsdoc */
class CustomElement extends HTMLElement {
constructor() {
super();
this.attachShadow({
mode: 'open'
});

(<ShadowRoot>this.shadowRoot).innerHTML = `<div><slot name="image"></slot></div>`;
const slot = (<ShadowRoot>this.shadowRoot).children[0].children[0];
slot.addEventListener('slotchange', () => {
lifecycle.push('slotchange.' + this.isConnected);
});
}

public connectedCallback(): void {
lifecycle.push('connected');
}

public disconnectedCallback(): void {
lifecycle.push('disconnected');
}
}
/* eslint-enable jsdoc/require-jsdoc */

window.customElements.define('custom-element', CustomElement);

const customElement = document.createElement('custom-element');

customElement.innerHTML = '<img slot="image" src="test.jpg" />';

document.body.appendChild(customElement);

expect(lifecycle).toEqual(['connected', 'slotchange.true']);
});
});
});

0 comments on commit e93af7a

Please sign in to comment.