# VoltX Plugin System Spec
## Overview
The plugin system enables extending the framework with custom `data-volt-*` attribute bindings.
Plugins follow the same binding patterns as core bindings (text, html, class, events) but can implement specialized behaviors like persistence, scrolling, and URL synchronization.
## Design Goals
### Extensibility
Plugins can access the full binding context including the DOM element, reactive scope, signal utilities, and cleanup registration.
### Explicit Opt-In
Built-in plugins require explicit registration to keep the core bundle minimal. Applications only load the functionality they use.
### Simplicity
Plugin API mirrors the internal binding handler signature. Developers who end up familiar with Volt internals can easily create plugins.
### Consistency
Plugins should integrate seamlessly with the mount/unmount lifecycle, cleanup system, and reactive primitives.
## Plugin API
### Registration
Plugins are registered using the `registerPlugin()` function:
```ts
registerPlugin(name: string, handler: PluginHandler): void
```
The plugin name becomes the `data-volt-*` attribute suffix. For example, registering a plugin named `"tooltip"` enables `data-volt-tooltip` attributes.
### Plugin Handler
Plugin handlers receive a context object and the attribute value:
```ts
type PluginHandler = (context: PluginContext, value: string) => void
```
The handler should:
1. Parse the attribute value
2. Set up bindings and subscriptions
3. Register cleanup functions for unmount
### PluginContext
The context object provides:
```ts
interface PluginContext {
element: Element; // The bound DOM element
scope: Scope; // Reactive scope with signals
addCleanup(fn: CleanupFunction): void; // Register cleanup
findSignal(path: string): Signal | undefined; // Locate signals by path
evaluate(expression: string): unknown; // Evaluate expressions
}
```
### Example: Custom Tooltip Plugin
```ts
import { registerPlugin } from 'voltx.js';
registerPlugin('tooltip', (context, value) => {
const tooltip = document.createElement('div');
tooltip.className = 'tooltip';
tooltip.textContent = context.evaluate(value);
const show = () => document.body.appendChild(tooltip);
const hide = () => tooltip.remove();
context.element.addEventListener('mouseenter', show);
context.element.addEventListener('mouseleave', hide);
context.addCleanup(() => {
hide();
context.element.removeEventListener('mouseenter', show);
context.element.removeEventListener('mouseleave', hide);
});
const signal = context.findSignal(value);
if (signal) {
const unsubscribe = signal.subscribe((newValue) => {
tooltip.textContent = String(newValue);
});
context.addCleanup(unsubscribe);
}
});
```
## Built-in Plugins
VoltX.js ships with three built-in plugins that must be explicitly registered.
### data-volt-persist
Synchronizes signal values with persistent storage (`localStorage`, `sessionStorage`, `IndexedDB`).
**Syntax:**
```html
```
**Storage Types:**
- `local` - localStorage (persistent across sessions)
- `session` - sessionStorage (cleared on tab close)
- `indexeddb` - IndexedDB (large datasets, async)
- Custom adapters via `registerStorageAdapter()`
**Behavior:**
1. On mount: Load persisted value into signal (if exists)
2. On signal change: Persist new value to storage
3. On unmount: Clean up storage listeners
**Examples:**
```html
```
Saves scroll position to the specified signal and restores on mount.
**Scroll-To:**
```html
```
Scrolls to element when the specified signal changes to match element's ID or selector.
**Scroll Spy:**
```html
```
Updates signal with boolean visibility state using Intersection Observer.
**Smooth Scrolling:**
```html
```
Enables smooth scrolling with configurable behavior from signal.
### data-volt-url
Synchronizes signal values with URL parameters and hash-based routing.
**Syntax:**
```html
```
**Behaviors:**
**Read URL Parameters:**
```html
```
Reads URL parameter on mount and sets signal value. Signal changes do not update URL.
**Bidirectional Sync:**
```html
```
You can also use the shorthand attribute form where the signal name is encoded in the attribute suffix:
```html
```
Changes to signal update URL parameter, changes to URL update signal. Uses History API for clean URLs.
**Hash Routing:**
```html
```
Keeps hash portion of URL in sync with signal. Useful for client-side routing.
**Notes:**
- Uses History API (`pushState`/`replaceState`) for param sync
- Listens to `popstate` for browser back/forward
- Debounces URL updates to avoid excessive history entries
- Automatically serializes/deserializes values (strings, numbers, booleans)
- Accepts `data-volt-url="mode:signal"` or `data-volt-url:signal="mode"` forms
- Supports `query`, `hash`, and `history` mode aliases in shorthand attributes (e.g., `data-volt-url:filter="query"`)
## Implementation
### Integration
The binder system checks the plugin registry before falling through to unknown attribute warnings
### Context
The binder creates a PluginContext from BindingContext:
```ts
function createPluginContext(bindingContext: BindingContext): PluginContext {
return {
element: bindingContext.element,
scope: bindingContext.scope,
addCleanup: (fn) => bindingContext.cleanups.push(fn),
findSignal: (path) => findSignalInScope(bindingContext.scope, path),
evaluate: (expr) => evaluate(expr, bindingContext.scope)
};
}
```
### Module Structure
```sh
src/
core/
plugin.ts # Plugin registry and API
binder.ts # Modified to integrate plugins
plugins/
persist.ts # Persistence plugin
scroll.ts # Scroll behavior plugin
url.ts # URL synchronization plugin
index.ts # Exports registerPlugin and built-in plugins
```
## Bundle Size Considerations
With explicit registration, applications control their bundle size:
- Core framework: ~15 KB gzipped (no plugins)
- Each plugin: ~1-3 KB gzipped
- Applications import only what they use
- Tree-shaking eliminates unused plugins
Example bundle breakdown:
```sh
volt/core : 15 KB
volt/plugins/persist : 2 KB
volt/plugins/scroll : 2.5 KB
volt/plugins/url : 1.5 KB
--------------------------------
Total (all plugins) : 21 KB
```
## Extension Points
Future plugin capabilities:
- Lifecycle hooks (beforeMount, afterMount, beforeUnmount)
- Plugin dependencies and composition
- Plugin configuration API
- Async plugin initialization
- Plugin registry