From d9c79cf444b6113fabce3aba75bd73d2b515a06d Mon Sep 17 00:00:00 2001 From: Adnaan Badr Date: Tue, 25 Aug 2026 19:38:28 +0000 Subject: [PATCH 1/5] feat(attributes): make lvt-* attributes extensible via a handler registry Adding an lvt-* attribute meant forking this library: writing a dom/*.ts module, importing it into livetemplate-client.ts, and inserting a call at the right position in a hardcoded ~20-entry post-render sequence, with a matching entry in disconnect()'s teardown list. An app that wanted lvt-x:copy-to-clipboard had no supported way to get one, and ~2,400 lines of handlers with exactly one consumer shipped to every LiveTemplate user because there was no mechanism for a downstream app to own them. Replace both hardcoded lists with a registry of handler objects and a public LiveTemplateClient.registerAttribute(). Every built-in registers through the same interface an external author uses -- there is no privileged internal path, so a gap in the public API shows up as a gap for the framework itself. Two layers. Declarative (declare an attribute NAME, get called per element) is what most authors should touch; the framework owns selector escaping, the scan, per-element tracking, empty-value rejection and sweep-on-removal, which makes the three classic handler bugs -- no sweep, listeners stacking every render, a captured transport that dies at reconnect -- unreachable from it. A selectors + setup()/teardown() escape hatch covers cross-element state; the built-ins use it, because their bodies are unchanged and their existing tests call them directly. Registration is static, and late registration catches up against the live DOM immediately: under the documented + + ``` + + The framework owns selector escaping, the DOM scan, per-element match + tracking, empty-value rejection, and sweeping elements that lost the attribute + or left the DOM — so the three classic mistakes (no sweep, listeners stacking + on every render, a captured transport that dies at reconnect) are not + reachable from this layer. `ctx.value` and `ctx.send` are live accessors, so a + listener wired once still reads the current attribute value and dispatches + through the current transport. + + Set `needsServerChannel: true` to get `ctx.send`; the message routes through + the same path an `lvt-on:click` action uses, and the server cannot tell the + difference. No server-side registration, no new wire message type, no build + step. + + A `selectors` + `setup()`/`teardown()` escape hatch covers handlers with + cross-element state; every built-in uses it, so an author is never on a lesser + API than the framework itself. + + Registration is static and late registration is supported: calling + `registerAttribute` after the client has connected runs the handler against + the live DOM immediately, rather than waiting for the next render. That is the + only sequence available to a second `