···44| ------- | ----- | ---------------------------------------------------------- | ------------------------------------------------------------------------ |
55| | ✓ | [Foundations](#foundations) | Initial project setup, tooling, and reactive signal prototype. |
66| | ✓ | [Reactivity & Bindings](#reactivity--bindings) | Core DOM bindings (`data-x-*`) and declarative updates. |
77-| | | [Actions & Effects](#actions--effects) | Event system and derived reactivity primitives. |
77+| | ✓ | [Actions & Effects](#actions--effects) | Event system and derived reactivity primitives. |
88| | | [Plugins Framework](#plugins-framework) | Modular plugin system and first built-in plugin set. |
99| | | [Streaming & Patch Engine](#streaming--patch-engine) | SSE/WebSocket JSON patch streaming. |
1010| | | [Persistence & Offline](#persistence--offline) | State persistence, storage sync, and fallback behaviors. |
···4545**Outcome:** Fully functional reactive UI layer with event bindings and computed updates.
4646**Deliverables:**
4747 - ✓ Event binding system (`data-x-on-*`)
4848- - `$el` and `$event` scoped references
4848+ - ✓ `$el` and `$event` scoped references
4949 - ✓ Derived signals (`computed`, `effect`)
5050- - Async effects (e.g., fetch triggers)
5050+ - ✓ Async effects (e.g., fetch triggers)
51515252### Plugins Framework
5353···5555**Outcome:** Stable plugin API enabling community-driven extensions.
5656**Deliverables:**
5757 - ✓ `registerPlugin(name, fn)` API
5858- - Context and lifecycle hooks
5858+ - ✓ Context and lifecycle hooks
5959 - ✓ Built-ins:
6060 - ✓ `data-x-persist`
6161 - ✓ `data-x-scroll`
6262 - ✓ `data-x-url`
6363 - ✓ Tests & registry
6464- - Example in docs/examples/plugins.md
6564 - ✓ Setup test coverage with generous thresholds (~50%)
6565+ - Example in docs/examples/plugins.md
6666 - End-to-end examples (counter, form, live field updates)
6767 - `docs/examples/reactivity.md`
6868 - `actions`, `effects`, `signals`
···7373**Outcome:** Volt.js can receive and apply live updates from the server
7474**Deliverables:**
7575 - JSON Patch parser and DOM applier
7676- - `data-x-stream` attribute
7676+ - `data-volt-stream` attribute
7777 - Reconnection/backoff logic
7878 - Raise test coverage threshold to 60%
7979 - Integration test with mock SSE server
···146146 - ✓ Binding directives for text, attributes, classes, styles, and two-way form controls (`data-volt-[bind|text|model|class:*]`).
147147 - ✓ Control-flow directives (`data-volt-for`, `data-volt-if`, `data-volt-else`) with lifecycle-safe teardown.
148148 - ✓ Declarative event system (`data-volt-on:*`) with helper surface for list mutations and plugin hooks.
149149- - SSR compatibility helpers and sandboxed expression evaluator per the security contract.
150150- - Integration tests covering TodoMVC and hydration edge cases.
149149+ - SSR compatibility helpers and sandboxed expression evaluator
151150152151## Examples
153152
+76
docs/api/events.md
···11+---
22+version: 1.0
33+updated: 2025-10-18
44+---
55+66+# Event Handling
77+88+Volt.js provides declarative event handling through `data-volt-on-*` attributes with automatic access to special scoped references.
99+1010+## Event Binding Syntax
1111+1212+Event handlers are attached using the `data-volt-on-{eventName}` attribute
1313+1414+The attribute value can be:
1515+1616+- A function reference from the scope: `handleClick`
1717+- An inline expression: `count.set(count.get() + 1)`
1818+- A method call: `myObject.method()`
1919+2020+## Scoped References
2121+2222+Event handlers have access to two special scoped references that are automatically injected:
2323+2424+### `$el` - The Target Element
2525+2626+The `$el` reference provides access to the DOM element that the event handler is bound to.
2727+2828+**Type:** [`Element`](https://developer.mozilla.org/en-US/docs/Web/API/Element)
2929+3030+### `$event` - The Event Object
3131+3232+The `$event` reference provides access to the native browser event object.
3333+3434+**Type:** [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event) (or specific event type like `MouseEvent`, `KeyboardEvent`, etc.)
3535+3636+## Event Types
3737+3838+Volt.js aims to support all standard DOM events through `data-volt-on-*`:
3939+4040+**Mouse Events:**
4141+4242+- `click`, `dblclick`
4343+- `mousedown`, `mouseup`
4444+- `mouseover`, `mouseout`, `mouseenter`, `mouseleave`
4545+- `mousemove`
4646+4747+**Keyboard Events:**
4848+4949+- `keydown`, `keyup`, `keypress`
5050+5151+**Form Events:**
5252+5353+- `submit`, `reset`
5454+- `input`, `change`
5555+- `focus`, `blur`
5656+5757+**Touch Events:**
5858+5959+- `touchstart`, `touchend`, `touchmove`, `touchcancel`
6060+6161+**Other Events:**
6262+6363+- `scroll`, `resize`
6464+- `load`, `error`
6565+- Any custom events
6666+6767+## Implementation Details
6868+6969+When an event handler is bound, Volt.js:
7070+7171+1. Creates a new scope that extends the component scope
7272+2. Injects `$el` (the bound element) and `$event` (the event object) into this scope
7373+3. Evaluates the expression in this enhanced scope
7474+4. If the expression returns a function, calls it with the event
7575+7676+The event listener is automatically cleaned up when the element is unmounted.
···11+/**
22+ * Global lifecycle hook system for Volt.js
33+ * Provides beforeMount, afterMount, beforeUnmount, and afterUnmount hooks
44+ */
55+66+import type { GlobalHookName, MountHookCallback, Scope, UnmountHookCallback } from "$types/volt";
77+88+/**
99+ * Global lifecycle hooks registry
1010+ */
1111+const lifecycleHooks = new Map<GlobalHookName, Set<MountHookCallback | UnmountHookCallback>>([
1212+ ["beforeMount", new Set()],
1313+ ["afterMount", new Set()],
1414+ ["beforeUnmount", new Set()],
1515+ ["afterUnmount", new Set()],
1616+]);
1717+1818+/**
1919+ * Register a global lifecycle hook.
2020+ * Global hooks run for every mount/unmount operation in the application.
2121+ *
2222+ * @param name - Name of the lifecycle hook
2323+ * @param cb - Callback function to execute
2424+ * @returns Unregister function
2525+ *
2626+ * @example
2727+ * // Log every mount operation
2828+ * registerGlobalHook('beforeMount', (root, scope) => {
2929+ * console.log('Mounting', root, 'with scope', scope);
3030+ * });
3131+ *
3232+ * @example
3333+ * // Track mounted elements
3434+ * const mountedElements = new Set<Element>();
3535+ * registerGlobalHook('afterMount', (root) => {
3636+ * mountedElements.add(root);
3737+ * });
3838+ * registerGlobalHook('beforeUnmount', (root) => {
3939+ * mountedElements.delete(root);
4040+ * });
4141+ */
4242+export function registerGlobalHook(name: GlobalHookName, cb: MountHookCallback | UnmountHookCallback): () => void {
4343+ const hooks = lifecycleHooks.get(name);
4444+ if (!hooks) {
4545+ throw new Error(`Unknown lifecycle hook: ${name}`);
4646+ }
4747+4848+ hooks.add(cb);
4949+5050+ return () => {
5151+ hooks.delete(cb);
5252+ };
5353+}
5454+5555+/**
5656+ * Unregister a global lifecycle hook.
5757+ *
5858+ * @param name - Name of the lifecycle hook
5959+ * @param cb - Callback function to remove
6060+ * @returns true if the hook was removed, false if it wasn't registered
6161+ */
6262+export function unregisterGlobalHook(name: GlobalHookName, cb: MountHookCallback | UnmountHookCallback): boolean {
6363+ const hooks = lifecycleHooks.get(name);
6464+ if (!hooks) {
6565+ return false;
6666+ }
6767+6868+ return hooks.delete(cb);
6969+}
7070+7171+/**
7272+ * Clear all global hooks for a specific lifecycle event.
7373+ *
7474+ * @param name - Name of the lifecycle hook to clear
7575+ */
7676+export function clearGlobalHooks(name: GlobalHookName): void {
7777+ const hooks = lifecycleHooks.get(name);
7878+ if (hooks) {
7979+ hooks.clear();
8080+ }
8181+}
8282+8383+export function clearAllGlobalHooks(): void {
8484+ for (const hooks of lifecycleHooks.values()) {
8585+ hooks.clear();
8686+ }
8787+}
8888+8989+/**
9090+ * Get all registered hooks for a specific lifecycle event.
9191+ * Used internally by the binder system.
9292+ *
9393+ * @param name - Name of the lifecycle hook
9494+ * @returns Array of registered callbacks
9595+ */
9696+export function getGlobalHooks(name: GlobalHookName): Array<MountHookCallback | UnmountHookCallback> {
9797+ const hooks = lifecycleHooks.get(name);
9898+ return hooks ? [...hooks] : [];
9999+}
100100+101101+/**
102102+ * Execute all registered hooks for a lifecycle event.
103103+ * Used internally by the binder system.
104104+ *
105105+ * @param hookName - Name of the lifecycle hook to execute
106106+ * @param root - The root element being mounted/unmounted
107107+ * @param scope - The scope object (only for mount hooks)
108108+ */
109109+export function executeGlobalHooks(hookName: GlobalHookName, root: Element, scope?: Scope): void {
110110+ const hooks = lifecycleHooks.get(hookName);
111111+ if (!hooks || hooks.size === 0) {
112112+ return;
113113+ }
114114+115115+ for (const callback of hooks) {
116116+ try {
117117+ if (hookName === "beforeMount" || hookName === "afterMount") {
118118+ if (scope !== undefined) {
119119+ (callback as MountHookCallback)(root, scope);
120120+ }
121121+ } else {
122122+ (callback as UnmountHookCallback)(root);
123123+ }
124124+ } catch (error) {
125125+ console.error(`Error in global ${hookName} hook:`, error);
126126+ }
127127+ }
128128+}
129129+130130+/**
131131+ * Element-level lifecycle tracking for per-element hooks
132132+ */
133133+type ElementLifecycleState = {
134134+ isMounted: boolean;
135135+ bindings: Set<string>;
136136+ onMount: Set<() => void>;
137137+ onUnmount: Set<() => void>;
138138+};
139139+140140+const elementLifecycleStates = new WeakMap<Element, ElementLifecycleState>();
141141+142142+/**
143143+ * Get or create lifecycle state for an element.
144144+ *
145145+ * @param element - The element to track
146146+ * @returns The lifecycle state object
147147+ */
148148+function getElementLifecycleState(element: Element): ElementLifecycleState {
149149+ let state = elementLifecycleStates.get(element);
150150+ if (!state) {
151151+ state = { isMounted: false, bindings: new Set(), onMount: new Set(), onUnmount: new Set() };
152152+ elementLifecycleStates.set(element, state);
153153+ }
154154+ return state;
155155+}
156156+157157+/**
158158+ * Register a per-element lifecycle hook.
159159+ * These hooks are specific to individual elements.
160160+ *
161161+ * @param element - The element to attach the hook to
162162+ * @param hookType - Type of hook ('mount' or 'unmount')
163163+ * @param cb - Callback to execute
164164+ */
165165+export function registerElementHook(element: Element, hookType: "mount" | "unmount", cb: () => void): void {
166166+ const state = getElementLifecycleState(element);
167167+168168+ if (hookType === "mount") {
169169+ state.onMount.add(cb);
170170+ } else {
171171+ state.onUnmount.add(cb);
172172+ }
173173+}
174174+175175+/**
176176+ * Notify that an element has been mounted.
177177+ * Executes all registered onMount callbacks for the element.
178178+ *
179179+ * @param element - The mounted element
180180+ */
181181+export function notifyElementMounted(element: Element): void {
182182+ const state = getElementLifecycleState(element);
183183+184184+ if (state.isMounted) {
185185+ return;
186186+ }
187187+188188+ state.isMounted = true;
189189+190190+ for (const callback of state.onMount) {
191191+ try {
192192+ callback();
193193+ } catch (error) {
194194+ console.error("Error in element onMount hook:", error);
195195+ }
196196+ }
197197+}
198198+199199+/**
200200+ * Notify that an element is being unmounted.
201201+ * Executes all registered onUnmount callbacks for the element.
202202+ *
203203+ * @param element - The element being unmounted
204204+ */
205205+export function notifyElementUnmounted(element: Element): void {
206206+ const state = getElementLifecycleState(element);
207207+208208+ if (!state.isMounted) {
209209+ return;
210210+ }
211211+212212+ state.isMounted = false;
213213+214214+ for (const callback of state.onUnmount) {
215215+ try {
216216+ callback();
217217+ } catch (error) {
218218+ console.error("Error in element onUnmount hook:", error);
219219+ }
220220+ }
221221+222222+ elementLifecycleStates.delete(element);
223223+}
224224+225225+/**
226226+ * Notify that a binding has been created on an element.
227227+ *
228228+ * @param element - The element the binding is on
229229+ * @param name - Name of the binding (e.g., 'text', 'class', 'on-click')
230230+ */
231231+export function notifyBindingCreated(element: Element, name: string): void {
232232+ const state = getElementLifecycleState(element);
233233+ state.bindings.add(name);
234234+}
235235+236236+/**
237237+ * Notify that a binding has been destroyed on an element.
238238+ *
239239+ * @param element - The element the binding was on
240240+ * @param name - Name of the binding
241241+ */
242242+export function notifyBindingDestroyed(element: Element, name: string): void {
243243+ const state = elementLifecycleStates.get(element);
244244+ if (state) {
245245+ state.bindings.delete(name);
246246+ }
247247+}
248248+249249+/**
250250+ * Check if an element is currently mounted.
251251+ *
252252+ * @param element - The element to check
253253+ * @returns true if the element is mounted
254254+ */
255255+export function isElementMounted(element: Element): boolean {
256256+ const state = elementLifecycleStates.get(element);
257257+ return state?.isMounted ?? false;
258258+}
259259+260260+/**
261261+ * Get all bindings on an element.
262262+ *
263263+ * @param element - The element to query
264264+ * @returns Array of binding names
265265+ */
266266+export function getElementBindings(element: Element): string[] {
267267+ const state = elementLifecycleStates.get(element);
268268+ return state ? [...state.bindings] : [];
269269+}
+21-1
lib/src/index.ts
···44 * @packageDocumentation
55 */
6677-export type { ChargedRoot, ChargeResult, ComputedSignal, PluginContext, PluginHandler, Signal } from "$types/volt";
77+export type {
88+ AsyncEffectFunction,
99+ AsyncEffectOptions,
1010+ ChargedRoot,
1111+ ChargeResult,
1212+ ComputedSignal,
1313+ GlobalHookName,
1414+ PluginContext,
1515+ PluginHandler,
1616+ Signal,
1717+} from "$types/volt";
1818+export { asyncEffect } from "@volt/core/asyncEffect";
819export { mount } from "@volt/core/binder";
920export { charge } from "@volt/core/charge";
2121+export {
2222+ clearAllGlobalHooks,
2323+ clearGlobalHooks,
2424+ getElementBindings,
2525+ isElementMounted,
2626+ registerElementHook,
2727+ registerGlobalHook,
2828+ unregisterGlobalHook,
2929+} from "@volt/core/lifecycle";
1030export { clearPlugins, getRegisteredPlugins, hasPlugin, registerPlugin, unregisterPlugin } from "@volt/core/plugin";
1131export { computed, effect, signal } from "@volt/core/signal";
1232export { persistPlugin, registerStorageAdapter, scrollPlugin, urlPlugin } from "@volt/plugins/index";
+27-89
lib/src/styles/base.css
···1111 * Inspired by: magick.css, latex-css, sakura, matcha, mvp.css
1212 */
13131414-/* ==========================================================================
1515- CSS Custom Properties - Design Tokens
1616- ========================================================================== */
1414+@import url('https://fonts.googleapis.com/css2?family=Google+Sans+Code:ital,wght@0,300..800;1,300..800&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&display=swap');
17151816/**
1917 * Root-level CSS variables define the design system.
···3028 --font-size-4xl: 2.027rem; /* 36.5px */
3129 --font-size-5xl: 2.566rem; /* 46.2px */
32303333- /* Font Families - Sans-serif with personality */
3434- /* System fonts for performance, fallback to serif for character */
3535- --font-sans: "Inter", "SF Pro Display", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3636- --font-serif: "Iowan Old Style", "Palatino Linotype", "URW Palladio L", P052, serif;
3737- --font-mono: "SF Mono", "Cascadia Code", "Fira Code", "Roboto Mono", Consolas, monospace;
3131+ --font-sans: "Inter", sans-serif;
3232+ --font-serif: "Libre Baskerville", serif;
3333+ --font-mono: "Google Sans Code", monospace;
38343935 /* Spacing Scale - Based on 0.5rem increments */
4036 --space-xs: 0.25rem; /* 4px */
···4541 --space-2xl: 3rem; /* 48px */
4642 --space-3xl: 4rem; /* 64px */
47434848- /* Line Heights - Optimized for readability */
4444+ /* Line Heights */
4945 --line-height-tight: 1.25;
5046 --line-height-base: 1.6;
5147 --line-height-relaxed: 1.8;
···6965 --color-warning: #bf8700;
7066 --color-error: #cb2431;
71677272- /* Shadows - Subtle depth */
7368 --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
7469 --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
7570 --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
···7974 --radius-md: 6px;
8075 --radius-lg: 8px;
81768282- /* Transitions */
8377 --transition-fast: 150ms ease-in-out;
8478 --transition-base: 250ms ease-in-out;
8579}
86808781/**
8882 * Dark Theme Overrides
8383+ *
8984 * Automatically applied when user prefers dark color scheme
9085 */
9186@media (prefers-color-scheme: dark) {
···109104 }
110105}
111106112112-/* ==========================================================================
113113- CSS Reset & Base Styles
114114- ========================================================================== */
115115-116107/**
117117- * Modern CSS reset with sensible defaults
108108+ * CSS reset
118109 */
119110*, *::before, *::after {
120111 box-sizing: border-box;
···153144 margin: 0 auto;
154145 padding: var(--space-2xl) var(--space-lg);
155146}
156156-157157-/* ==========================================================================
158158- Typography - Hierarchy & Rhythm
159159- ========================================================================== */
160147161148/**
162149 * Headings hierarchy
···215202216203/**
217204 * First paragraph after headings - No top margin
218218- * Common convention in academic typography
205205+ *
206206+ * Inspired by tufte.css
219207 */
220208h1 + p, h2 + p, h3 + p, h4 + p, h5 + p, h6 + p {
221209 margin-top: 0;
222210}
223211224212/**
225225- * Links - Accessible and distinctive
213213+ * Links
214214+ *
226215 * Uses accent color with underline for clarity
227216 */
228217a {
···265254266255/**
267256 * Subscript and superscript
257257+ *
268258 * Prevents them from affecting line height
269259 */
270260sub, sup {
···291281 color: var(--color-text-muted);
292282}
293283294294-/* ==========================================================================
295295- Lists - Ordered & Unordered
296296- ========================================================================== */
297297-298284/**
299285 * List spacing and indentation
300300- * Nested lists inherit proper spacing
286286+ *
287287+ * Nested lists inherit spacing
301288 */
302289ul, ol {
303290 margin-bottom: var(--space-lg);
···344331 color: var(--color-text-muted);
345332}
346333347347-/* ==========================================================================
348348- Tufte-Style Sidenotes
349349- ========================================================================== */
350350-351334/**
352335 * Sidenotes using <small> elements
353336 * On desktop: positioned in right margin
···431414}
432415433416blockquote cite::before {
417417+ /* TODO: fix */
434418 content: " ";
435419}
436420437437-/* ==========================================================================
438438- Code & Preformatted Text
439439- ========================================================================== */
440440-441421/**
442422 * Inline code
443443- * Monospace font with subtle background for distinction
423423+ *
424424+ * Monospace font with subtle background
444425 */
445426code {
446427 font-family: var(--font-mono);
···453434454435/**
455436 * Keyboard input
437437+ *
456438 * Styled like keys on a keyboard
457439 */
458440kbd {
···484466485467/**
486468 * Preformatted code blocks
469469+ *
487470 * Horizontal scrolling for overflow, no word wrap
488471 */
489472pre {
···504487 font-size: 0.875rem;
505488}
506489507507-/* ==========================================================================
508508- Horizontal Rules
509509- ========================================================================== */
510490511491/**
512492 * Section dividers
513513- * Centered decorative element with breathing room
493493+ *
494494+ * Centered decorative element
514495 */
515496hr {
516497 margin: var(--space-3xl) auto;
···518499 border-top: 1px solid var(--color-border);
519500 max-width: 50%;
520501}
521521-522522-/* ==========================================================================
523523- Tables
524524- ========================================================================== */
525502526503/**
527504 * Table container for horizontal scrolling on small screens
···537514538515/**
539516 * Table header styling
540540- * Bold text with bottom border for separation
517517+ *
518518+ * Bold text with bottom border
541519 */
542520thead {
543521 background-color: var(--color-bg-alt);
···574552 transition: background-color var(--transition-fast);
575553}
576554577577-/* ==========================================================================
578578- Forms & Input Elements
579579- ========================================================================== */
580580-581555/**
582556 * Form container spacing
583557 */
···604578605579/**
606580 * Labels
581581+ *
607582 * Block display for better touch targets
608583 */
609584label {
···625600626601/**
627602 * Text inputs and textareas
628628- * Consistent sizing and interaction states
629603 */
630604input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]),
631605select,
···645619646620/**
647621 * Focus states for inputs
622622+ *
648623 * Clear visual feedback for keyboard navigation
649624 */
650625input:focus,
···714689 overflow: hidden;
715690}
716691717717-/* ==========================================================================
718718- Buttons
719719- ========================================================================== */
720720-721692/**
722693 * Button styling
723694 * Primary action style with hover and active states
···789760 outline-offset: 2px;
790761}
791762792792-/* ==========================================================================
793793- Media Elements
794794- ========================================================================== */
795795-796763/**
797764 * Images
765765+ *
798766 * Responsive by default, maintains aspect ratio
799767 */
800768img {
···823791824792/**
825793 * Video and audio
826826- * Responsive and accessible
827794 */
828795video, audio {
829796 max-width: 100%;
830797 margin: var(--space-xl) 0;
831798}
832799833833-/**
834834- * Canvas and SVG
835835- */
836800canvas, svg {
837801 max-width: 100%;
838802 height: auto;
839803}
840804841841-/* ==========================================================================
842842- Embedded Content
843843- ========================================================================== */
844844-845845-/**
846846- * iframe - Responsive wrapper
847847- */
848805iframe {
849806 max-width: 100%;
850807 border: 1px solid var(--color-border);
851808 border-radius: var(--radius-md);
852809 margin: var(--space-xl) 0;
853810}
854854-855855-/* ==========================================================================
856856- Semantic HTML5 Elements
857857- ========================================================================== */
858811859812/**
860813 * Article and Section
···894847 color: var(--color-text-muted);
895848}
896849897897-/**
898898- * Nav
899899- * Navigation menus
900900- */
901850nav {
902851 margin: var(--space-lg) 0;
903852}
···916865917866/**
918867 * Details and Summary
868868+ *
919869 * Disclosure widget for expandable content
920870 */
921871details {
···944894 border-bottom: 1px solid var(--color-border);
945895}
946896947947-/* ==========================================================================
948948- Utility Classes (Minimal, for framework integration)
949949- ========================================================================== */
950950-951897/**
952898 * Screen reader only
953899 * Hides content visually but keeps it accessible to assistive technology
···964910 border-width: 0;
965911}
966912967967-/* ==========================================================================
968968- Print Styles
969969- ========================================================================== */
970970-971913/**
972914 * Print-specific optimizations
973915 */
···1009951 max-width: 100% !important;
1010952 }
1011953}
10121012-10131013-/* ==========================================================================
10141014- Responsive Breakpoints
10151015- ========================================================================== */
10169541017955/**
1018956 * Tablet and below - Reduce spacing
+93
lib/src/types/volt.d.ts
···3939 * Handles simple property paths, literals, and signal unwrapping.
4040 */
4141 evaluate(expression: string): unknown;
4242+4343+ /**
4444+ * Lifecycle hooks for plugin-specific mount/unmount behavior
4545+ */
4646+ lifecycle: PluginLifecycle;
4247}
43484449/**
···117122export type ChargeResult = { roots: ChargedRoot[]; cleanup: CleanupFunction };
118123119124export type Dep = { get: () => unknown; subscribe: (callback: (value: unknown) => void) => () => void };
125125+126126+/**
127127+ * Options for configuring async effects
128128+ */
129129+export interface AsyncEffectOptions {
130130+ /**
131131+ * Enable automatic AbortController integration.
132132+ * When true, provides an AbortSignal to the effect function for canceling async operations.
133133+ */
134134+ abortable?: boolean;
135135+136136+ /**
137137+ * Debounce delay in milliseconds.
138138+ * Effect execution is delayed until this duration has passed without dependencies changing.
139139+ */
140140+ debounce?: number;
141141+142142+ /**
143143+ * Throttle delay in milliseconds.
144144+ * Effect execution is rate-limited to at most once per this duration.
145145+ */
146146+ throttle?: number;
147147+148148+ /**
149149+ * Error handler for async effect failures.
150150+ * Receives the error and a retry function.
151151+ */
152152+ onError?: (error: Error, retry: () => void) => void;
153153+154154+ /**
155155+ * Number of automatic retry attempts on error.
156156+ * Defaults to 0 (no retries).
157157+ */
158158+ retries?: number;
159159+160160+ /**
161161+ * Delay in milliseconds between retry attempts.
162162+ * Defaults to 0 (immediate retry).
163163+ */
164164+ retryDelay?: number;
165165+}
166166+167167+/**
168168+ * Async effect function signature.
169169+ * Receives an optional AbortSignal when abortable option is enabled.
170170+ * Can return a cleanup function or a Promise that resolves to a cleanup function.
171171+ */
172172+export type AsyncEffectFunction = (signal?: AbortSignal) => Promise<void | (() => void)>;
173173+174174+/**
175175+ * Lifecycle hook callback types
176176+ */
177177+export type LifecycleHookCallback = () => void;
178178+export type MountHookCallback = (root: Element, scope: Scope) => void;
179179+export type UnmountHookCallback = (root: Element) => void;
180180+export type ElementMountHookCallback = (element: Element, scope: Scope) => void;
181181+export type ElementUnmountHookCallback = (element: Element) => void;
182182+export type BindingHookCallback = (element: Element, bindingName: string) => void;
183183+184184+/**
185185+ * Lifecycle hook names
186186+ */
187187+export type GlobalHookName = "beforeMount" | "afterMount" | "beforeUnmount" | "afterUnmount";
188188+189189+/**
190190+ * Extended plugin context with lifecycle hooks
191191+ */
192192+export interface PluginLifecycle {
193193+ /**
194194+ * Register a callback to run when the plugin is initialized for an element
195195+ */
196196+ onMount: (callback: LifecycleHookCallback) => void;
197197+198198+ /**
199199+ * Register a callback to run when the element is being unmounted
200200+ */
201201+ onUnmount: (callback: LifecycleHookCallback) => void;
202202+203203+ /**
204204+ * Register a callback to run before the binding is created
205205+ */
206206+ beforeBinding: (callback: LifecycleHookCallback) => void;
207207+208208+ /**
209209+ * Register a callback to run after the binding is created
210210+ */
211211+ afterBinding: (callback: LifecycleHookCallback) => void;
212212+}