UNPKG

lume

Version:

Build next-level interactive web applications.

111 lines 4.82 kB
import { Constructor } from 'lowclass/dist/Constructor.js'; import { observeChildren } from './utils/observeChildren.js'; // TODO add childAttributeChangedCallback, similar to attributeChangedCallback? /** * @class ChildTracker - A mixin for use with custom elements, for tracking * children of a custom element. In a similar pattern as with custom element * `connectedCallback` and `disconnectedCallback`, when a child is connected * `childConnectedCallback(child)` will be called and when a child is * disconnected `childDisconnectedCallback(child)` will be called. */ export function ChildTracker(Base) { return class ChildTracker extends Constructor(Base) { /** * @property {boolean} awaitChildrenDefined When `true`, * `childConnectedCallback`s will not fire until those children are * upgraded (if they are possibly custom elements, having a hyphen in * their name). If a child is disconnected before it has been upgraded, * then `childDisconnectedCallback` will not be called and any pending * `childConnectedCallback` will be canceled. */ awaitChildrenDefined = true; /** * @property {boolean} syncChildCallbacks When `true`, * `childConnectedCallback`s will be fired for existing children when * this element is connected into the DOM, and * `childDisconnectedCallback`s will be fired when `this` is * disconnected from the DOM. * * This can be useful for instantiation and cleanup logic that depends * on children, not just when children are (dis)connected, but any time * this element is (dis)connected. Sometimes children are not custom * elements, and don't have their own (dis)connectedCallbacks, hence the * need in such cases for a parent to manage setup/cleanup logic based on * the connection of non-custom elements. */ syncChildCallbacks = true; constructor(...args) { super(...args); this.#createObserver(); } connectedCallback() { super.connectedCallback?.(); if (this.syncChildCallbacks) this.#runChildConnectedCallbacks(); this.#createObserver(); } disconnectedCallback() { super.disconnectedCallback?.(); if (this.syncChildCallbacks) this.#runChildDisconnectedCallbacks(); this.#destroyObserver(); } #awaitedChildren = new Set(); #runChildConnectedCallbacks() { for (let el = this.firstElementChild; el; el = el.nextElementSibling) this.#runChildConnect(el); } #runChildConnect(child) { const elementIsUpgraded = child.matches(':defined'); if (elementIsUpgraded || !this.awaitChildrenDefined) { this.childConnectedCallback?.(child); } else { if (!this.#awaitedChildren.has(child)) { this.#awaitedChildren.add(child); customElements.whenDefined(child.tagName.toLowerCase()).then(() => { this.#awaitedChildren.delete(child); if (!this.isConnected) return; this.childConnectedCallback?.(child); }); } } } #runChildDisconnectedCallbacks() { for (let el = this.firstElementChild; el; el = el.nextElementSibling) this.#runChildDisconnect(el); } #runChildDisconnect(child) { if (this.#awaitedChildren.has(child)) return; this.childDisconnectedCallback?.(child); } #unobserveChildren = null; #createObserver() { if (this.#unobserveChildren) return; // observeChildren returns a MutationObserver observing childList this.#unobserveChildren = observeChildren({ target: this, onConnect: (child) => { if (!this.isConnected) return; this.childConnectedCallback && this.childConnectedCallback(child); }, onDisconnect: (child) => { if (!this.isConnected) return; this.childDisconnectedCallback && this.childDisconnectedCallback(child); }, }); } #destroyObserver() { if (!this.#unobserveChildren) return; this.#unobserveChildren(); this.#unobserveChildren = null; } }; } //# sourceMappingURL=ChildTracker.js.map