Skip to main content

max / makenotwork

1.3 KB · 34 lines History Blame Raw
1 // Islands infrastructure.
2 //
3 // An "island" is a custom element that attaches behavior to server-rendered
4 // markup — it never re-renders the server's HTML (no hydration). A key payoff
5 // over the old `querySelectorAll` + `DOMContentLoaded` + `htmx:afterSwap`
6 // re-init dance: the browser upgrades a custom element automatically whenever
7 // it's connected to the document, INCLUDING content HTMX swaps in, so islands
8 // need no manual re-initialization after a swap.
9 //
10 // Pure, testable logic goes in a sibling `*.logic.ts` (no DOM imports) so
11 // `node --test` can exercise it without a DOM; the element file wires that
12 // logic over the light-DOM children.
13
14 /** Base class: runs `init()` exactly once, the first time the element is
15 * connected (a move/reconnect won't re-run it). */
16 export abstract class MnwElement extends HTMLElement {
17 #initialized = false;
18
19 connectedCallback(): void {
20 if (this.#initialized) return;
21 this.#initialized = true;
22 this.init();
23 }
24
25 /** Wire behavior over the already-rendered light-DOM children. */
26 protected abstract init(): void;
27 }
28
29 /** Define a custom element once (idempotent — safe if the module is imported
30 * from more than one entry). */
31 export function define(name: string, ctor: CustomElementConstructor): void {
32 if (!customElements.get(name)) customElements.define(name, ctor);
33 }
34