| 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 |
|