import { HTMLElementComponent } from "./element"; import { MaybeChildNode } from "./node"; import { DOMPortalComponent } from "./portal"; /** * The root component of the app. * * There should be only one root component, * representing the element with id passed too the `App` constructor. * * This component manages top-level components of the app and portals. */ export class DOMRootComponent extends HTMLElementComponent< keyof HTMLElementTagNameMap > { /** * Portals that have not been updated to the DOM tree. */ pendingPortals: DOMPortalComponent[] = []; /** * Portals that is currently mounted to the DOM tree. */ protected mountedPortals = new Set(); addEventListener( type: K & string, listener: (this: HTMLAnchorElement, ev: HTMLElementEventMap[K]) => void, options?: boolean | AddEventListenerOptions, ) { // @ts-expect-error return super.addEventListener(type, listener, options); } updateDOM(): MaybeChildNode { // Apply cls and css, register event listeners, and update direct children. let lastNode = super.updateDOM(); const portalsToRemove = this.mountedPortals; for (const portal of this.pendingPortals) { // Update the portal. lastNode = portal.updateMount(lastNode); // Do not remove the portal. portalsToRemove.delete(portal); } // Remove unused portals. for (const unusedPortal of portalsToRemove) { unusedPortal.unmount(); } this.mountedPortals = new Set(this.pendingPortals); // Reset the pending portals for the next `UPDATE` call. this.pendingPortals = []; return lastNode; } insertAfter(_node: ChildNode): never { throw new Error("Cannot insert root component after another node."); } prependTo(_parent: Element): never { throw new Error("Cannot prepend root component to DOM tree."); } removeFrom(_parent: Element): never { throw new Error("Cannot remove root component from DOM tree."); } }