a reactive (signals based) hypermedia web framework (wip) stormlightlabs.github.io/volt/
hypermedia frontend signals
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs: added overview

+273 -3
+11 -3
docs/.vitepress/config.ts
··· 6 6 description: "A reactive, hypermedia framework.", 7 7 appearance: "dark", 8 8 themeConfig: { 9 - nav: [{ text: "Home", link: "/" }, { text: "CSS", link: "/css/volt-css" }, { text: "API", link: "/api-examples" }], 9 + nav: [ 10 + { text: "Home", link: "/" }, 11 + { text: "Overview", link: "/overview" }, 12 + { text: "CSS", link: "/css/volt-css" }, 13 + { text: "API", link: "/api-examples" }, 14 + ], 10 15 sidebar: [ 11 - { text: "Getting Started", items: [{ text: "Introduction", link: "/" }] }, 16 + { 17 + text: "Getting Started", 18 + items: [{ text: "Introduction", link: "/" }, { text: "Overview", link: "/overview" }], 19 + }, 12 20 { 13 21 text: "CSS", 14 22 collapsed: false, 15 23 items: [{ text: "Volt CSS", link: "/css/volt-css" }, { text: "CSS Reference", link: "/css/semantics" }], 16 24 }, 17 25 { text: "API Reference", collapsed: false, items: [{ text: "Runtime API", link: "/api-examples" }] }, 18 - { text: "Examples", collapsed: true, items: [{ text: "Markdown Examples", link: "/markdown-examples" }] }, 26 + { text: "Plugin System", collapsed: false, items: [{ text: "Plugin Spec", link: "/plugin-spec" }] }, 19 27 ], 20 28 socialLinks: [{ icon: "github", link: "https://github.com/stormlightlabs/volt" }], 21 29 },
+138
docs/overview.md
··· 1 + --- 2 + outline: deep 3 + --- 4 + 5 + # Framework Overview 6 + 7 + Volt.js is a lightweight, hypermedia based reactive framework for building declarative UIs. 8 + 9 + It combines HTML-driven behavior via `data-x-*` attributes with signal-based reactivity. 10 + 11 + ## Architecture 12 + 13 + The framework consists of three layers: 14 + 15 + ### Reactivity Layer 16 + 17 + Signals are the foundation of Volt's reactive system: 18 + 19 + ```js 20 + import { signal, computed, effect } from 'volt'; 21 + 22 + const count = signal(0); 23 + const doubled = computed(() => count.get() * 2, [count]); 24 + 25 + effect(() => console.log('Count:', count.get()), [count]); 26 + ``` 27 + 28 + - **`signal()`** creates mutable reactive state 29 + - **`computed()`** derives values from signals 30 + - **`effect()`** runs side effects when dependencies change 31 + 32 + No reactivity scheduler. Signals notify subscribers directly on change. 33 + 34 + ### Binding System 35 + 36 + Bindings connect signals to DOM via `data-x-*` attributes: 37 + 38 + ```html 39 + <div id="app"> 40 + <p data-x-text="count">0</p> 41 + <button data-x-on-click="increment">+</button> 42 + <div data-x-if="isPositive">Positive</div> 43 + </div> 44 + ``` 45 + 46 + ```js 47 + mount(document.querySelector('#app'), { 48 + count, 49 + isPositive, 50 + increment: () => count.set(count.get() + 1) 51 + }); 52 + ``` 53 + 54 + Core bindings: 55 + 56 + - `data-x-text` - Update text content 57 + - `data-x-html` - Update HTML content 58 + - `data-x-class` - Toggle CSS classes 59 + - `data-x-on-*` - Attach event handlers 60 + - `data-x-if` - Conditional rendering 61 + - `data-x-for` - List rendering 62 + 63 + ### Plugin System 64 + 65 + Extend functionality via custom `data-x-*` bindings: 66 + 67 + ```js 68 + import { registerPlugin } from 'volt'; 69 + 70 + registerPlugin('tooltip', (context, value) => { 71 + // Custom binding logic 72 + }); 73 + ``` 74 + 75 + See [Plugin Spec](./plugin-spec.md) for details. 76 + 77 + ## Key Concepts 78 + 79 + ### Signals Update DOM Directly 80 + 81 + Bindings subscribe to signals and update the real DOM when values change. 82 + 83 + ### HTML Drives Behavior 84 + 85 + Declare UI structure and interactivity in HTML. JavaScript provides state and handlers. 86 + 87 + ### Explicit Dependencies 88 + 89 + Computed signals and effects declare dependencies explicitly: 90 + 91 + ```js 92 + computed(() => a.get() + b.get(), [a, b]) // Both deps listed 93 + ``` 94 + 95 + ### Cleanup Management 96 + 97 + `mount()` returns a cleanup function. All bindings register their cleanup to prevent memory leaks: 98 + 99 + ```js 100 + const cleanup = mount(element, scope); 101 + // Later: 102 + cleanup(); // Unsubscribes all bindings 103 + ``` 104 + 105 + ## Examples 106 + 107 + ### Counter 108 + 109 + Simple counter demonstrating basic reactivity: 110 + 111 + - Location: `examples/counter/` 112 + - Shows: signals, computed values, conditional rendering, class bindings 113 + 114 + ### TodoMVC 115 + 116 + Complete todo app with filtering and editing: 117 + 118 + - Location: `examples/todomvc/` 119 + - Shows: list rendering, event handling, computed filters, state mapping 120 + - Uses: Volt CSS (classless framework) for styling 121 + 122 + ## Design Constraints 123 + 124 + - Core runtime under 15 KB gzipped 125 + - Zero dependencies 126 + - No custom build systems 127 + - TypeScript source 128 + - Every feature tested 129 + 130 + ## Browser Support 131 + 132 + Modern browsers with support for: 133 + 134 + - ES modules 135 + - Proxy objects 136 + - CSS custom properties 137 + 138 + Chrome 90+, Firefox 88+, Safari 14+
+1
eslint.config.js
··· 28 28 "eslint.config.js", 29 29 "vite.config.ts", 30 30 "./examples/**", 31 + "./docs/**", 31 32 ], 32 33 rules: { 33 34 "no-undef": "off",
+43
examples/counter/README.md
··· 1 + # Counter 2 + 3 + Simple interactive counter demonstrating Volt.js reactive primitives. 4 + 5 + ## Features 6 + 7 + - Signal-based state with `count` tracking the current value 8 + - Computed values deriving `doubled` and `squared` from count 9 + - Conditional rendering showing status (Positive/Negative/Zero) 10 + - Dynamic class binding for visual feedback 11 + 12 + ## Running 13 + 14 + 1. Build the project: `pnpm build` from root 15 + 2. Open `index.html` in a browser 16 + 17 + ## Implementation 18 + 19 + The counter uses three Volt.js primitives: 20 + 21 + **Signals** store reactive state: 22 + 23 + ```js 24 + const count = signal(0); 25 + ``` 26 + 27 + **Computed** derive values automatically: 28 + 29 + ```js 30 + const doubled = computed(() => count.get() * 2, [count]); 31 + const isPositive = computed(() => count.get() > 0, [count]); 32 + ``` 33 + 34 + **Bindings** connect state to DOM: 35 + 36 + ```html 37 + <span data-x-text="count">0</span> 38 + <button data-x-on-click="increment">+</button> 39 + <div data-x-if="isPositive">Positive</div> 40 + <div data-x-class="countClass"></div> 41 + ``` 42 + 43 + When `count` changes, all dependent computed values recalculate and bindings update automatically.
+80
examples/todomvc/README.md
··· 1 + # TodoMVC 2 + 3 + Complete TodoMVC implementation using Volt.js and Volt CSS (classless framework). 4 + 5 + ## Features 6 + 7 + - Add/edit/delete todos 8 + - Mark complete/incomplete 9 + - Filter by All/Active/Completed 10 + - Toggle all at once 11 + - Clear completed 12 + - Persistent reactive state 13 + 14 + ## Running 15 + 16 + 1. Build the project: `pnpm build` from root 17 + 2. Open `index.html` in a browser 18 + 19 + ## Implementation 20 + 21 + ### State Management 22 + 23 + Two base signals control all state: 24 + 25 + ```js 26 + const todos = signal([...]); 27 + const filter = signal('all'); 28 + ``` 29 + 30 + Computed signals derive UI state: 31 + 32 + ```js 33 + const filteredTodos = computed(() => { 34 + if (filter.get() === 'active') return todos.get().filter(t => !t.completed); 35 + if (filter.get() === 'completed') return todos.get().filter(t => t.completed); 36 + return todos.get(); 37 + }, [todos, filter]); 38 + 39 + const activeCount = computed(() => 40 + todos.get().filter(t => !t.completed).length, 41 + [todos] 42 + ); 43 + ``` 44 + 45 + ### List Rendering 46 + 47 + `data-x-for` renders todos reactively: 48 + 49 + ```html 50 + <li data-x-for="(todo, index) in filteredTodos" data-x-class="getTodoClass(todo)"> 51 + <input type="checkbox" data-x-on-click="toggleTodo($event, index)"> 52 + <label data-x-text="todo.text"></label> 53 + </li> 54 + ``` 55 + 56 + ### Event Handling 57 + 58 + All interactions use declarative bindings: 59 + 60 + - `data-x-on-click` for buttons and checkboxes 61 + - `data-x-on-keyup` for Enter/Escape in inputs 62 + - `data-x-on-dblclick` for editing mode 63 + - `data-x-on-blur` to save edits 64 + 65 + ### Index Mapping 66 + 67 + Because the UI displays filtered todos but operations need the full array, handlers map filtered indices to actual positions: 68 + 69 + ```js 70 + const deleteTodo = (indexInFiltered) => { 71 + const filteredList = filteredTodos.get(); 72 + const todoToFind = filteredList[indexInFiltered]; 73 + const actualIndex = todos.get().findIndex(t => t.id === todoToFind.id); 74 + todos.set(todos.get().filter((_, i) => i !== actualIndex)); 75 + }; 76 + ``` 77 + 78 + ### Styling 79 + 80 + Uses only Volt CSS. Semantic HTML elements are styled automatically with no custom classes needed.