lightview 1.8.2 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/.codacy/cli.sh +149 -0
  2. package/.codacy/codacy.yaml +15 -0
  3. package/.github/instructions/codacy.instructions.md +72 -0
  4. package/.wranglerignore +21 -0
  5. package/README.md +1330 -19
  6. package/_headers +4 -0
  7. package/build.js +70 -0
  8. package/components/actions/button.js +151 -0
  9. package/components/actions/dropdown.js +120 -0
  10. package/components/actions/modal.js +146 -0
  11. package/components/actions/swap.js +118 -0
  12. package/components/daisyui.js +288 -0
  13. package/components/data-display/accordion.js +128 -0
  14. package/components/data-display/alert.js +112 -0
  15. package/components/data-display/avatar.js +170 -0
  16. package/components/data-display/badge.js +82 -0
  17. package/components/data-display/card.js +151 -0
  18. package/components/data-display/carousel.js +94 -0
  19. package/components/data-display/chart.js +220 -0
  20. package/components/data-display/chat.js +128 -0
  21. package/components/data-display/collapse.js +103 -0
  22. package/components/data-display/countdown.js +69 -0
  23. package/components/data-display/diff.js +111 -0
  24. package/components/data-display/kbd.js +65 -0
  25. package/components/data-display/loading.js +75 -0
  26. package/components/data-display/progress.js +79 -0
  27. package/components/data-display/radial-progress.js +88 -0
  28. package/components/data-display/skeleton.js +66 -0
  29. package/components/data-display/stats.js +159 -0
  30. package/components/data-display/table.js +146 -0
  31. package/components/data-display/timeline.js +146 -0
  32. package/components/data-display/toast.js +72 -0
  33. package/components/data-display/tooltip.js +74 -0
  34. package/components/data-input/checkbox.js +253 -0
  35. package/components/data-input/file-input.js +224 -0
  36. package/components/data-input/input.js +264 -0
  37. package/components/data-input/radio.js +338 -0
  38. package/components/data-input/range.js +204 -0
  39. package/components/data-input/rating.js +219 -0
  40. package/components/data-input/select.js +287 -0
  41. package/components/data-input/textarea.js +287 -0
  42. package/components/data-input/toggle.js +201 -0
  43. package/components/index.js +137 -0
  44. package/components/layout/divider.js +72 -0
  45. package/components/layout/drawer.js +142 -0
  46. package/components/layout/footer.js +100 -0
  47. package/components/layout/hero.js +109 -0
  48. package/components/layout/indicator.js +90 -0
  49. package/components/layout/join.js +78 -0
  50. package/components/layout/navbar.js +110 -0
  51. package/components/navigation/breadcrumbs.js +91 -0
  52. package/components/navigation/dock.js +103 -0
  53. package/components/navigation/menu.js +126 -0
  54. package/components/navigation/pagination.js +105 -0
  55. package/components/navigation/steps.js +89 -0
  56. package/components/navigation/tabs.css +177 -0
  57. package/components/navigation/tabs.js +123 -0
  58. package/components/theme/theme-switch.css +65 -0
  59. package/components/theme/theme-switch.js +177 -0
  60. package/docs/about.html +164 -0
  61. package/docs/api/computed.html +184 -0
  62. package/docs/api/effects.html +173 -0
  63. package/docs/api/elements.html +180 -0
  64. package/docs/api/enhance.html +225 -0
  65. package/docs/api/hypermedia.html +165 -0
  66. package/docs/api/index.html +178 -0
  67. package/docs/api/nav.html +18 -0
  68. package/docs/api/signals.html +136 -0
  69. package/docs/api/state.html +217 -0
  70. package/docs/assets/images/logo-favicon.svg +42 -0
  71. package/docs/assets/images/logo-static.svg +40 -0
  72. package/docs/assets/images/logo.svg +66 -0
  73. package/docs/assets/js/examplify.js +395 -0
  74. package/docs/assets/styles/site.css +1102 -0
  75. package/docs/assets/styles/themes.css +236 -0
  76. package/docs/components/accordion.html +439 -0
  77. package/docs/components/alert.html +528 -0
  78. package/docs/components/avatar.html +586 -0
  79. package/docs/components/badge.html +531 -0
  80. package/docs/components/breadcrumbs.html +278 -0
  81. package/docs/components/button.html +579 -0
  82. package/docs/components/card.html +561 -0
  83. package/docs/components/carousel.html +286 -0
  84. package/docs/components/chart-area.html +702 -0
  85. package/docs/components/chart-bar.html +782 -0
  86. package/docs/components/chart-column.html +735 -0
  87. package/docs/components/chart-line.html +794 -0
  88. package/docs/components/chart-pie.html +823 -0
  89. package/docs/components/chart.html +610 -15
  90. package/docs/components/chat.html +547 -0
  91. package/docs/components/checkbox.html +641 -0
  92. package/docs/components/collapse.html +536 -0
  93. package/docs/components/component-nav.html +53 -0
  94. package/docs/components/countdown.html +470 -0
  95. package/docs/components/diff.html +245 -0
  96. package/docs/components/divider.html +240 -0
  97. package/docs/components/dock.html +277 -0
  98. package/docs/components/drawer.html +515 -0
  99. package/docs/components/dropdown.html +479 -0
  100. package/docs/components/file-input.html +591 -0
  101. package/docs/components/footer.html +301 -0
  102. package/docs/components/gallery.html +504 -0
  103. package/docs/components/hero.html +264 -0
  104. package/docs/components/index.css +840 -0
  105. package/docs/components/index.html +735 -0
  106. package/docs/components/indicator.html +342 -0
  107. package/docs/components/input.html +644 -0
  108. package/docs/components/join.html +285 -0
  109. package/docs/components/kbd.html +322 -0
  110. package/docs/components/loading.html +521 -0
  111. package/docs/components/menu.html +461 -0
  112. package/docs/components/modal.html +639 -0
  113. package/docs/components/navbar.html +321 -0
  114. package/docs/components/pagination.html +279 -0
  115. package/docs/components/progress.html +514 -0
  116. package/docs/components/radial-progress.html +434 -0
  117. package/docs/components/radio.html +655 -0
  118. package/docs/components/range.html +611 -0
  119. package/docs/components/rating.html +642 -0
  120. package/docs/components/select.html +696 -0
  121. package/docs/components/sidebar-setup.js +93 -0
  122. package/docs/components/skeleton.html +447 -0
  123. package/docs/components/spinner.html +68 -0
  124. package/docs/components/stats.html +486 -0
  125. package/docs/components/steps.html +356 -0
  126. package/docs/components/swap.html +517 -0
  127. package/docs/components/switch.html +68 -0
  128. package/docs/components/table.html +668 -0
  129. package/docs/components/tabs.html +506 -0
  130. package/docs/components/text-input.html +68 -0
  131. package/docs/components/textarea.html +603 -0
  132. package/docs/components/timeline.html +485 -42
  133. package/docs/components/toast.html +474 -0
  134. package/docs/components/toggle.html +564 -0
  135. package/docs/components/tooltip.html +423 -0
  136. package/docs/examples/getting-started-example.html +40 -0
  137. package/docs/examples/index.html +93 -0
  138. package/docs/getting-started/index.html +739 -0
  139. package/docs/getting-started/reviews.html +23 -0
  140. package/docs/getting-started/reviews.odom +108 -0
  141. package/docs/getting-started/reviews.vdom +84 -0
  142. package/docs/index.html +132 -42
  143. package/docs/playground.html +416 -0
  144. package/docs/router.html +285 -0
  145. package/docs/styles/index.html +190 -0
  146. package/functions/_middleware.js +32 -0
  147. package/index.html +309 -0
  148. package/lightview-router.js +364 -0
  149. package/lightview-x.js +1577 -0
  150. package/lightview.js +659 -1200
  151. package/middleware/locale.js +25 -0
  152. package/middleware/markdown.js +44 -0
  153. package/middleware/notFound.js +37 -0
  154. package/package.json +27 -41
  155. package/watch.js +92 -0
  156. package/wrangler.toml +12 -0
  157. package/.idea/lightview.iml +0 -12
  158. package/.idea/modules.xml +0 -8
  159. package/.idea/vcs.xml +0 -6
  160. package/LICENSE +0 -21
  161. package/codepen-no-tabs-embed.css +0 -2
  162. package/docs/CNAME +0 -1
  163. package/docs/api.html +0 -674
  164. package/docs/blank.html +0 -10
  165. package/docs/comparedto.html +0 -89
  166. package/docs/components/chart-repl.html +0 -69
  167. package/docs/components/components.js +0 -113
  168. package/docs/components/contents.html +0 -17
  169. package/docs/components/gantt-repl.html +0 -61
  170. package/docs/components/gantt.html +0 -42
  171. package/docs/components/gauge-repl.html +0 -66
  172. package/docs/components/gauge.html +0 -20
  173. package/docs/components/orgchart-repl.html +0 -64
  174. package/docs/components/orgchart.html +0 -41
  175. package/docs/components/repl-as-src.html +0 -17
  176. package/docs/components/repl-repl.html +0 -95
  177. package/docs/components/repl.html +0 -527
  178. package/docs/components/timeline-repl.html +0 -72
  179. package/docs/components.html +0 -14
  180. package/docs/css/highlightjs.min.css +0 -9
  181. package/docs/css/tutorial.css +0 -35
  182. package/docs/examples/anchor.html +0 -11
  183. package/docs/examples/chart.html +0 -34
  184. package/docs/examples/counter.html +0 -26
  185. package/docs/examples/counter.test.mjs +0 -47
  186. package/docs/examples/counter2.html +0 -26
  187. package/docs/examples/directives.html +0 -79
  188. package/docs/examples/foreign.html +0 -50
  189. package/docs/examples/forgeinform.html +0 -98
  190. package/docs/examples/form.html +0 -61
  191. package/docs/examples/gauge.html +0 -18
  192. package/docs/examples/invalid-template-literals.html +0 -44
  193. package/docs/examples/medium/remote.html +0 -60
  194. package/docs/examples/message.html +0 -18
  195. package/docs/examples/nested.html +0 -11
  196. package/docs/examples/object-bound-form.html +0 -34
  197. package/docs/examples/remote-server.js +0 -51
  198. package/docs/examples/remote.html +0 -34
  199. package/docs/examples/remote.json +0 -1
  200. package/docs/examples/scratch.html +0 -69
  201. package/docs/examples/sensors/index.html +0 -44
  202. package/docs/examples/sensors/sensor-server.js +0 -30
  203. package/docs/examples/shared.html +0 -41
  204. package/docs/examples/template.html +0 -33
  205. package/docs/examples/timeline.html +0 -21
  206. package/docs/examples/todo.html +0 -40
  207. package/docs/examples/top.html +0 -10
  208. package/docs/examples/types.html +0 -94
  209. package/docs/examples/xor.html +0 -62
  210. package/docs/examples.html +0 -25
  211. package/docs/javascript/codejar.min.js +0 -8
  212. package/docs/javascript/highlightjs.min.js +0 -1173
  213. package/docs/javascript/isomorphic-git.js +0 -9
  214. package/docs/javascript/json5.min.js +0 -1
  215. package/docs/javascript/lightning-fs.js +0 -1
  216. package/docs/javascript/lightview.js +0 -1285
  217. package/docs/javascript/marked.min.js +0 -6
  218. package/docs/javascript/peerjs.min.js +0 -70
  219. package/docs/javascript/turndown.js +0 -973
  220. package/docs/javascript/types.js +0 -606
  221. package/docs/javascript/utils.js +0 -45
  222. package/docs/lightview.html +0 -63
  223. package/docs/old_index.html +0 -965
  224. package/docs/old_index.md +0 -1132
  225. package/docs/slidein.html +0 -51
  226. package/docs/tutorial/0-getting-started.html +0 -67
  227. package/docs/tutorial/1-intro-to-variables.html +0 -103
  228. package/docs/tutorial/10-template-components.html +0 -80
  229. package/docs/tutorial/11-linked-components.html +0 -76
  230. package/docs/tutorial/12-imported-components.html +0 -67
  231. package/docs/tutorial/13-input-binding.html +0 -94
  232. package/docs/tutorial/14-automatic-variable-creation.html +0 -74
  233. package/docs/tutorial/15-form-binding.html +0 -110
  234. package/docs/tutorial/16-if-directive.html +0 -60
  235. package/docs/tutorial/17-loop-directives.html +0 -83
  236. package/docs/tutorial/18-sanitizing-and-escaping-input.html +0 -79
  237. package/docs/tutorial/2-imported-and-exported-variables.html +0 -80
  238. package/docs/tutorial/3-data-types.html +0 -89
  239. package/docs/tutorial/4-extended-data-types.html +0 -83
  240. package/docs/tutorial/5-extended-functional-types.html +0 -96
  241. package/docs/tutorial/5.1-extended-functional-types.html +0 -79
  242. package/docs/tutorial/5.2-extended-functional-types.html +0 -70
  243. package/docs/tutorial/6-conventional-javascript.html +0 -75
  244. package/docs/tutorial/7-monitoring-with-observers.html +0 -107
  245. package/docs/tutorial/8-event-listeners.html +0 -65
  246. package/docs/tutorial/9-intro-to-components.html +0 -91
  247. package/docs/tutorial/contents.html +0 -32
  248. package/docs/tutorial/my-component.html +0 -29
  249. package/docs/tutorial/remote-value.json +0 -4
  250. package/docs/websiterepl.html +0 -46
  251. package/jest-puppeteer.config.js +0 -5
  252. package/jest.config.json +0 -12
  253. package/lightview.min.js +0 -1
  254. package/lightview_good.js +0 -1267
  255. package/lightview_optimized.js +0 -1274
  256. package/repl_hold.html +0 -320
  257. package/test/basic.html +0 -104
  258. package/test/basic.test.mjs +0 -315
  259. package/test/extended.html +0 -29
  260. package/test/extended.test.mjs +0 -448
  261. package/types.js +0 -607
  262. package/unsplash.key +0 -1
package/README.md CHANGED
@@ -1,41 +1,1352 @@
1
- # lightview v1.8.2
1
+ <!-- SEO-friendly SPA Shim -->
2
+ <script src="./lightview-router.js"></script>
3
+ <script>
4
+ if (window.LightviewRouter) {
5
+ LightviewRouter.base('index.html');
6
+ }
7
+ </script>
8
+ # Lightview: README.md
2
9
 
3
- Small, simple, powerful web UI and micro front end creation ...
10
+ A lightweight reactive UI library with signal-based reactivity and a clean API. Build dynamic UIs with automatic DOM synchronization.
4
11
 
5
- Great ideas from Svelte, React, Vue and Riot combined into one small (but not tiny) tool: < 8K (minified/gzipped).
12
+ Access the full documentaion at [lightview.dev](/index.html).
6
13
 
7
- See the docs and examples at [https://lightview.dev](https://lightview.dev).
14
+ **Core**: ~6KB | **With Hypermedia Extensions and Component Library Support**: ~18KB total
8
15
 
9
- Meanwhile, here is what you get:
16
+ Fast: This [gallery of components](/docs/components) loads in about 1 second:
10
17
 
11
- 1) No pre-deployment transpilation/compilation required.
18
+ ## Modular Architecture
12
19
 
13
- 1) No virtual DOM. The Lightview dependency tracker targets just those nodes that need updates.
20
+ Lightview is split into two files:
14
21
 
15
- 1) Reactive string template literals for content and attribute value replacement.
22
+ - **`lightview.js`** - Core reactivity (signals, state, effects, elements)
23
+ - **`lightview-x.js`** - Hypermedia extension (src fetching, href navigation, template literals, named registries, Object DOM syntax, UI component library support)
16
24
 
17
- 1) Automatic import, export and cross-component syn for attributes/variables.
25
+ ### API Behavior
18
26
 
19
- 1) Svelte like variable usage, i.e. write your state modifying code like normal code.
27
+ | Usage | Core Only | With `-x` |
28
+ |-------|-----------|-----------|
29
+ | `signal(5)` | ✅ Works | ✅ Works |
30
+ | `signal(5, "count")` | ⚠️ Ignores name | ✅ Registers |
31
+ | `signal.get("count")` | ❌ Undefined | ✅ Works |
32
+ | `signal.get("count", 0)` | ❌ Undefined | ✅ Creates if missing |
33
+ | `state({...})` | ✅ Works | ✅ Works |
34
+ | `state({...}, "app")` | ⚠️ Ignores name | ✅ Registers |
35
+ | `state.get("app")` | ❌ Undefined | ✅ Works |
36
+ | `state.get("app", {...})` | ❌ Undefined | ✅ Creates if missing |
37
+ | `"${signal.get('count').value}"` | ❌ No processing | ✅ Reactive template |
38
+ | `<div src="page.html">` | ❌ No fetching | ✅ Loads content |
39
+ | `<span href="other.html">` | ❌ No navigation | ✅ Reactive navigation |
40
+ | `{ div: { class: "x" } }` | ❌ Not recognized | ✅ Object DOM syntax |
41
+ | `enhance('#btn', {...})` | ❌ Undefined | ✅ Enhances existing DOM |
20
42
 
21
- 1) TypeScript like runtime type checking of variables in components.
43
+ ### Installation
22
44
 
23
- 1) Extended variable type declarations including `min`, `max` and `step` on `number` or limits on `string` and `array` lengths.
45
+ ```html
46
+ <!-- Core only (reactivity) -->
47
+ <script src="lightview.js"></script>
24
48
 
25
- 1) Automatic server retrieval and update of variables declared as remote5.
49
+ <!-- Full features (hypermedia + templates) -->
50
+ <script src="lightview.js"></script>
51
+ <script src="lightview-x.js"></script>
52
+ ```
26
53
 
27
- 1) Single file and template components.
54
+ ## Core Concepts
28
55
 
29
- 1) Automatic input field variable creation and bindinge.
56
+ **Lightview** provides four ways to build UIs:
30
57
 
31
- 1) Attribute directives like l-if, and a powerful l-for that handles array and object keys, values, and entries.
58
+ 1. **Tagged API** - Concise, Bau.js-style syntax: `tags.div(...)`
59
+ 2. **Element Function** - Explicit: `element('div', {}, [...])`
60
+ 3. **vDOM Syntax** - JSON data structures: `{ tag: "div", attributes: {}, children: [] }`
61
+ 4. **Object DOM Syntax** *(lightview-x)* - Compact: `{ div: { class: "foo", children: [] } }`
32
62
 
33
- 1) Sandboxed remote components and micro front ends.
63
+ All four approaches use the same underlying reactive system based on **signals**.
34
64
 
35
- 1) Unit testable components and a debug mode for using standard JavaScript debuggers.
65
+ ## Installation
36
66
 
37
- 1) A component library including charts and gauges that work in Markdown files.
67
+ ```html
68
+ <script src="lightview.js"></script>
69
+ ```
38
70
 
71
+ ## Quick Start
39
72
 
73
+ ### Style 1: Tagged API
40
74
 
75
+ ```javascript
76
+ const lv = new Lightview();
77
+ const { signal, computed, tags } = lv;
78
+ const { div, h1, p, button } = tags;
41
79
 
80
+ const count = signal(0);
81
+ const doubled = computed(() => count.value * 2);
82
+
83
+ const app = div({ class: 'container' },
84
+ h1('Counter App'),
85
+ p(() => `Count: ${count.value}`),
86
+ p(() => `Doubled: ${doubled.value}`),
87
+ button({ onclick: () => count.value++ }, 'Increment'),
88
+ button({ onclick: () => count.value-- }, 'Decrement')
89
+ );
90
+
91
+ document.body.appendChild(app.domEl);
92
+ ```
93
+
94
+ ### Style 2: Element Function
95
+
96
+ ```javascript
97
+ const { signal, element } = new Lightview();
98
+
99
+ const count = signal(0);
100
+
101
+ const app = element('div', { class: 'container' }, [
102
+ element('h1', {}, ['Counter App']),
103
+ element('p', {}, [() => `Count: ${count.value}`]),
104
+ element('button', {
105
+ onclick: () => count.value++
106
+ }, ['Increment'])
107
+ ]);
108
+
109
+ document.body.appendChild(app.domEl);
110
+ ```
111
+
112
+ ### Style 3: vDOM Syntax (Plain JSON)
113
+
114
+ ```javascript
115
+ const { signal, element } = new Lightview();
116
+
117
+ const count = signal(0);
118
+
119
+ const app = element('div', { class: 'container' }, [
120
+ {
121
+ tag: 'h1',
122
+ attributes: {},
123
+ children: ['Counter App']
124
+ },
125
+ {
126
+ tag: 'p',
127
+ attributes: {},
128
+ children: [() => `Count: ${count()}`] // or count.value
129
+ },
130
+ {
131
+ tag: 'button',
132
+ attributes: { onclick: () => count.value++ }, // or count(count() + 1)
133
+ children: ['Increment']
134
+ }
135
+ ]);
136
+
137
+
138
+ document.body.appendChild(app.domEl);
139
+ ```
140
+
141
+ ### Style 4: Object DOM Syntax (lightview-x)
142
+
143
+ Object DOM syntax provides a more compact way to define elements. Instead of `{ tag, attributes, children }`, you use `{ tag: { ...attributes, children } }`.
144
+
145
+ **Requires lightview-x.js** and must be enabled:
146
+
147
+ ```javascript
148
+ // Enable Object DOM syntax (call once at startup)
149
+ LightviewX.useObjectDOMSyntax(); // Non-strict mode (default)
150
+ LightviewX.useObjectDOMSyntax(true); // Strict mode - validates HTML tag names
151
+ ```
152
+
153
+ ```javascript
154
+ const { signal, element, tags } = Lightview;
155
+ const { div, button } = tags;
156
+
157
+ // Enable Object DOM syntax
158
+ LightviewX.useObjectDOMSyntax();
159
+
160
+ const count = signal(0);
161
+
162
+ // Object DOM syntax in children arrays
163
+ const app = div({ class: 'container' },
164
+ { h1: { children: ['Counter App'] } },
165
+ { p: { children: [() => `Count: ${count.value}`] } },
166
+ { button: { onclick: () => count.value++, children: ['Increment'] } }
167
+ );
168
+
169
+ document.body.appendChild(app.domEl);
170
+ ```
171
+
172
+ **Comparison:**
173
+
174
+ | vDOM Syntax | Object DOM Syntax |
175
+ |-------------|-------------------|
176
+ | `{ tag: 'div', attributes: { class: 'box' }, children: ['Hello'] }` | `{ div: { class: 'box', children: ['Hello'] } }` |
177
+
178
+ **Pros & Cons:**
179
+
180
+ | Aspect | vDOM Syntax | Object DOM Syntax |
181
+ |--------|-------------|-------------------|
182
+ | **Verbosity** | More verbose | More compact |
183
+ | **Explicit** | ✅ Clear structure, easy to validate | ⚠️ Tag name is a dynamic key |
184
+ | **Serialization** | ✅ Easy to serialize/deserialize | ⚠️ Requires detection logic |
185
+ | **Reserved words** | ✅ None - `children` is just a property | ⚠️ `children` is reserved |
186
+ | **TypeScript** | ✅ Easy to type | ⚠️ Harder to provide autocomplete |
187
+ | **Dynamic tags** | ✅ `{ tag: myVar, ... }` | ⚠️ Requires `{ [myVar]: {...} }` |
188
+ | **Multiple elements** | ✅ Can have array of objects | ⚠️ One element per object |
189
+ | **Readability** | Familiar to React/vDOM users | Cleaner for static templates |
190
+
191
+ **Why vDOM is the default:**
192
+
193
+ 1. **Unambiguous parsing** - The presence of `tag` clearly identifies an element. Object DOM requires heuristics to detect (single key that's a valid tag name).
194
+
195
+ 2. **No reserved attribute names** - In vDOM, you can have an attribute literally named `children`. In Object DOM, `children` is reserved for child elements.
196
+
197
+ 3. **Better for data interchange** - vDOM objects can be safely serialized to JSON and parsed back without any special handling. They're self-describing.
198
+
199
+ 4. **Predictable validation** - Easy to check `if (obj.tag)` vs. finding the unknown key and checking if it's a valid tag.
200
+
201
+ 5. **Works without extensions** - vDOM is supported by core `lightview.js`. Object DOM requires `lightview-x.js`.
202
+
203
+ Object DOM is ideal for **hand-written templates** where brevity matters, or **configuration files** where you want a cleaner syntax. Use vDOM when you need **programmatic generation**, **serialization**, or **maximum compatibility**.
204
+
205
+ **Nested Example:**
206
+
207
+ ```javascript
208
+ // Object DOM - compact and readable
209
+ { div: {
210
+ class: 'card',
211
+ children: [
212
+ { h2: { children: ['Title'] } },
213
+ { p: { style: 'color: gray', children: ['Description'] } },
214
+ { button: { onclick: handleClick, children: ['Action'] } }
215
+ ]
216
+ }}
217
+
218
+ // Equivalent vDOM
219
+ { tag: 'div', attributes: { class: 'card' }, children: [
220
+ { tag: 'h2', attributes: {}, children: ['Title'] },
221
+ { tag: 'p', attributes: { style: 'color: gray' }, children: ['Description'] },
222
+ { tag: 'button', attributes: { onclick: handleClick }, children: ['Action'] }
223
+ ]}
224
+ ```
225
+
226
+ **Strict Mode:**
227
+
228
+ When `useObjectDOMSyntax(true)` is called, tag names are validated using the browser's own HTML parser. Unknown tags like `foo` or `notreal` will be rejected, while standard HTML tags and valid custom elements (with hyphens) are accepted.
229
+
230
+ ```javascript
231
+ LightviewX.useObjectDOMSyntax(true); // Enable strict validation
232
+
233
+ // Valid - browser recognizes these
234
+ { div: { children: ['OK'] } } // Standard HTML tag
235
+ { 'my-widget': { children: ['OK'] } } // Custom element (valid in browser)
236
+
237
+ // Invalid in strict mode (HTMLUnknownElement - won't be detected as Object DOM)
238
+ { notarealtag: { children: ['Nope'] } } // Browser returns HTMLUnknownElement
239
+
240
+ // You can also check directly:
241
+ LightviewX.isKnownHTMLTag('div'); // true
242
+ LightviewX.isKnownHTMLTag('my-widget'); // true (custom elements are valid)
243
+ LightviewX.isKnownHTMLTag('faketag'); // false (HTMLUnknownElement)
244
+ ```
245
+
246
+ ### Style 5: Component Functions
247
+
248
+ The `tag` property (or Object DOM key) can be a **function** instead of a string. This enables reusable components that return HTML, DOM nodes, vDOM, or Object DOM.
249
+
250
+ ```javascript
251
+ const { element, signal, tags } = Lightview;
252
+ const { div, button } = tags;
253
+
254
+ // Define a component function
255
+ const Card = (props) => ({
256
+ div: {
257
+ class: 'card',
258
+ style: `border: 1px solid ${props.borderColor || '#ccc'}; padding: 16px;`,
259
+ children: [
260
+ { h3: { children: [props.title] } },
261
+ { p: { children: [props.description] } },
262
+ ...(props.children || [])
263
+ ]
264
+ }
265
+ });
266
+
267
+ // Use component with element() - tag is a function
268
+ const app = element('div', {}, [
269
+ { tag: Card, attributes: { title: 'Hello', description: 'A card component' } },
270
+ { tag: Card, attributes: { title: 'World', borderColor: 'blue', children: [
271
+ button({ onclick: () => alert('Clicked!') }, 'Click Me')
272
+ ]}}
273
+ ]);
274
+ ```
275
+
276
+ **Component Return Types:**
277
+
278
+ Components can return any of these formats:
279
+
280
+ ```javascript
281
+ // 1. Object DOM (recommended for simplicity)
282
+ const Badge = (props) => ({
283
+ span: { class: 'badge', children: [props.text] }
284
+ });
285
+
286
+ // 2. vDOM
287
+ const Badge = (props) => ({
288
+ tag: 'span',
289
+ attributes: { class: 'badge' },
290
+ children: [props.text]
291
+ });
292
+
293
+ // 3. HTML string
294
+ const Badge = (props) => `<span class="badge">${props.text}</span>`;
295
+
296
+ // 4. DOM node
297
+ const Badge = (props) => {
298
+ const el = document.createElement('span');
299
+ el.className = 'badge';
300
+ el.textContent = props.text;
301
+ return el;
302
+ };
303
+ ```
304
+
305
+ **Registering Components:**
306
+
307
+ If you add a component to Ligtvhiew.tags, then you can treate it ligke any other tag. It will even work with Object DOM syntax.
308
+
309
+ ```javascript
310
+ // Register individual components
311
+ Lightview.tags['Badge'] = Badge;
312
+ ```
313
+
314
+ ```javascript
315
+ const { div, Badge } = tags;
316
+ const app = div(
317
+ Badge({ text: 'New' })
318
+ );
319
+ ```
320
+
321
+ ```javascript
322
+ // Now use by name in Object DOM syntax
323
+ LightviewX.useObjectDOMSyntax();
324
+
325
+ const app2 = div(
326
+ { Badge: { text: 'New' } }
327
+ );
328
+ ```
329
+
330
+ **With Global Scope:**
331
+
332
+ ```javascript
333
+ // Define component globally
334
+ window.Alert = (props) => ({
335
+ div: {
336
+ class: `alert alert-${props.type || 'info'}`,
337
+ children: [props.message]
338
+ }
339
+ });
340
+
341
+ // Enable global lookup
342
+ LightviewX.useObjectDOMSyntax();
343
+
344
+ // Use directly
345
+ const app = div(
346
+ { Alert: { type: 'success', message: 'Operation completed!' } }
347
+ );
348
+ ```
349
+
350
+ ## API Reference
351
+
352
+ ### Lightview Class
353
+
354
+ ```javascript
355
+ const lv = new Lightview();
356
+ ```
357
+
358
+ Creates a Lightview instance with:
359
+ - `signal(value)` - Create reactive state
360
+ - `computed(fn)` - Create derived state
361
+ - `effect(fn)` - Run side effects
362
+ - `state(obj)` - Create deep reactive store
363
+ - `element(tag, attrs, children)` - Create elements
364
+ - `tags` - Proxy for creating elements: `tags.div()`, `tags.button()`, etc.
365
+
366
+ ### Signals
367
+
368
+ Signals in Lightview are versatile. They can be used as **function calls** or by accessing the `.value` property. This dual API allows for flexible coding styles.
369
+
370
+ ```javascript
371
+ const name = signal('John');
372
+
373
+ // 1. Property Access Style
374
+ // Read
375
+ console.log(name.value); // 'John'
376
+ // Write
377
+ name.value = 'Jane';
378
+
379
+ // 2. Function Call Style
380
+ // Read
381
+ console.log(name()); // 'Jane'
382
+ // Write
383
+ name('Bob');
384
+ ```
385
+
386
+ You can choose whichever style you prefer or mix them as needed. Both methods are fully reactive and interoperable.
387
+
388
+ ### Working with Objects
389
+
390
+ Lightview signals are **shallow** by design. This keeps the library extremely small and fast (performant). When working with nested objects, use immutable patterns to update state:
391
+
392
+ ```javascript
393
+ const user = signal({ name: "Joe", address: { city: "NYC" } });
394
+
395
+ // ❌ Don't mutate directly (won't trigger updates)
396
+ // user.value.address.city = "LA";
397
+
398
+ // ✅ Do use immutable patterns
399
+ user.value = {
400
+ ...user.value,
401
+ address: { ...user.value.address, city: "LA" }
402
+ };
403
+ ```
404
+
405
+
406
+ ### Computed Signals
407
+
408
+ ```javascript
409
+ const firstName = signal('John');
410
+ const lastName = signal('Doe');
411
+
412
+ const fullName = computed(() => `${firstName.value} ${lastName.value}`);
413
+
414
+ console.log(fullName.value); // 'John Doe'
415
+ firstName.value = 'Jane';
416
+ console.log(fullName.value); // 'Jane Doe'
417
+ ```
418
+
419
+ ### State (Store)
420
+
421
+ For deeply nested objects or grouped state, use `state()`. It creates a reactive proxy where every property is automatically backed by a signal.
422
+
423
+ ```javascript
424
+ /*
425
+ Creates a deep reactive store.
426
+ Accessing properties (e.g. s.user.name) automatically tracks dependencies.
427
+ Setting properties automatically triggers updates.
428
+ */
429
+ const appState = state({
430
+ user: {
431
+ name: 'Alice',
432
+ settings: { theme: 'dark' }
433
+ },
434
+ count: 0
435
+ });
436
+
437
+ // Reading tracks dependencies
438
+ effect(() => {
439
+ console.log(`${appState.user.name} likes ${appState.user.settings.theme} mode`);
440
+ });
441
+
442
+ // Writing triggers updates
443
+ appState.user.name = 'Bob';
444
+ appState.user.settings.theme = 'light';
445
+ ```
446
+
447
+ Note: `state` objects read/write like normal JavaScript objects (no `.value` needed).
448
+
449
+ ### Reactive Arrays and Dates
450
+
451
+ **Lightview has a unique feature**: it can make Arrays and Date objects fully reactive, even when using their native methods. This is something most reactive libraries don't support!
452
+
453
+ #### Reactive Arrays
454
+
455
+ When you wrap an array in `state()`, Lightview automatically tracks changes to the array's `length` property. This means array mutation methods like `push()`, `pop()`, `splice()`, `shift()`, `unshift()`, etc. will trigger reactive updates:
456
+
457
+ ```javascript
458
+ // Style 1: Tagged API
459
+ const { state, effect, tags } = new Lightview();
460
+ const { div, ul, li, button } = tags;
461
+
462
+ const items = state(['Apple', 'Banana']);
463
+
464
+ // This effect automatically re-runs when the array length changes
465
+ effect(() => {
466
+ console.log(`Array has ${items.length} items`);
467
+ });
468
+
469
+ // All these methods trigger reactivity!
470
+ items.push('Cherry'); // Logs: "Array has 3 items"
471
+ items.pop(); // Logs: "Array has 2 items"
472
+ items.splice(1, 0, 'Date'); // Logs: "Array has 3 items"
473
+
474
+ // Use in UI
475
+ const app = div(
476
+ () => ul(
477
+ ...items.map(item => li(item))
478
+ ),
479
+ button({ onclick: () => items.push('New Item') }, 'Add Item')
480
+ );
481
+ ```
482
+
483
+ **Array elements now have full deep reactivity!** Both the array's `length` and individual element properties are tracked:
484
+
485
+ ```javascript
486
+ // Style 3: Plain JSON - state works with any API style
487
+ const { state, element } = new Lightview();
488
+
489
+ const items = state([
490
+ { name: 'Item 1', done: false },
491
+ { name: 'Item 2', done: true }
492
+ ]);
493
+
494
+ // Using Plain JSON structure with reactive list:
495
+ const list = element('div', {}, [
496
+ // Wrap in a function to re-render when items.length changes
497
+ () => ({
498
+ tag: 'ul',
499
+ attributes: {},
500
+ children: items.map(item => ({
501
+ tag: 'li',
502
+ attributes: { class: () => item.done ? 'completed' : '' },
503
+ children: [() => item.name]
504
+ }))
505
+ }),
506
+ {
507
+ tag: 'button',
508
+ attributes: { onclick: () => items.push({ name: `Item ${items.length + 1}`, done: false }) },
509
+ children: ['Add Item']
510
+ },
511
+ {
512
+ tag: 'button',
513
+ attributes: { onclick: () => items[0].done = !items[0].done },
514
+ children: ['Toggle First']
515
+ }
516
+ ]);
517
+
518
+ // All of these trigger UI updates:
519
+ // items.push({ name: 'Item 3', done: false }); // ✅ Reactive (length changed)
520
+ // items[0].done = true; // ✅ Reactive (element property changed)
521
+ // items[1].name = 'Updated Item'; // ✅ Reactive (nested property changed)
522
+ ```
523
+
524
+ #### Reactive Dates
525
+
526
+ Date objects are notoriously difficult to make reactive in most frameworks because their mutation methods (`setDate()`, `setHours()`, etc.) change internal state without changing the object reference. Lightview solves this by monitoring the `getTime()` value:
527
+
528
+ ```javascript
529
+ // Style 2: Element Function
530
+ const { state, effect, element } = new Lightview();
531
+
532
+ const currentDate = state(new Date());
533
+
534
+ // This effect re-runs whenever the date's timestamp changes
535
+ effect(() => {
536
+ console.log(`Current time: ${currentDate.getTime()}`);
537
+ });
538
+
539
+ // All date mutation methods trigger reactivity!
540
+ currentDate.setHours(12); // Triggers update
541
+ currentDate.setDate(15); // Triggers update
542
+ currentDate.setFullYear(2025); // Triggers update
543
+
544
+ // Use in UI
545
+ const clock = element('div', {}, [
546
+ element('p', {}, [() => `Time: ${currentDate.toLocaleTimeString()}`]),
547
+ element('button', {
548
+ onclick: () => currentDate.setTime(Date.now())
549
+ }, ['Update to Now'])
550
+ ]);
551
+ ```
552
+
553
+ **Why this matters**: In most reactive libraries (Vue, Solid, Svelte), you'd need to create a new Date object to trigger updates:
554
+
555
+ ```javascript
556
+ // What you'd have to do in Vue/Solid: Although, it will still work in Lightview
557
+ myDate.value = new Date(myDate.value.setHours(12)); // ❌ Awkward!
558
+
559
+ // What you can do in Lightview:
560
+ myDate.setHours(12); // ✅ Just works!
561
+ ```
562
+
563
+ ### Effects
564
+
565
+
566
+ ```javascript
567
+ const count = signal(0);
568
+
569
+ effect(() => {
570
+ console.log(`Count is now: ${count.value}`);
571
+ });
572
+
573
+ count.value = 1; // Logs: "Count is now: 1"
574
+ ```
575
+
576
+ ### Element Structure
577
+
578
+ Every element has:
579
+ - `tag` - HTML tag name or component function
580
+ - `attributes` - Object of attributes
581
+ - `children` - Array of child elements/text/functions
582
+ - `domEl` - The actual DOM node (read-only getter)
583
+
584
+
585
+
586
+ ## Reactive Features
587
+
588
+ ### Reactive Text
589
+
590
+ ```javascript
591
+ // Style 2: Element Function
592
+ const { element, signal } = new Lightview();
593
+
594
+ const name = signal('World');
595
+
596
+ const app = element('div', {}, [
597
+ element('h1', {}, [() => `Hello, ${name.value}!`]),
598
+ element('button', { onclick: () => name.value = 'Lightview' }, ['Change Name'])
599
+ ]);
600
+ // Click the button to see the greeting update
601
+ ```
602
+
603
+ ### Reactive Attributes
604
+
605
+ ```javascript
606
+ // Style 1: Tagged API
607
+ const { tags, signal } = new Lightview();
608
+ const { div, button } = tags;
609
+
610
+ const isActive = signal(false);
611
+
612
+ const app = div(
613
+ button({
614
+ class: () => isActive.value ? 'active' : 'inactive',
615
+ disabled: () => !isActive.value
616
+ }, 'I am toggled'),
617
+ button({ onclick: () => isActive.value = !isActive.value }, 'Toggle Active')
618
+ );
619
+ // Click 'Toggle Active' to enable/disable the first button
620
+ ```
621
+
622
+ ### Reactive Styles
623
+
624
+ ```javascript
625
+ // Style 3: Plain JSON
626
+ const { element, signal } = new Lightview();
627
+
628
+ const color = signal('blue');
629
+
630
+ const box = element('div', {}, [
631
+ {
632
+ tag: 'div',
633
+ attributes: {
634
+ style: () => ({
635
+ backgroundColor: color.value,
636
+ padding: '20px',
637
+ transition: 'background-color 0.3s'
638
+ })
639
+ },
640
+ children: [() => `Color: ${color.value}`]
641
+ },
642
+ {
643
+ tag: 'button',
644
+ attributes: { onclick: () => color.value = 'red' },
645
+ children: ['Red']
646
+ },
647
+ {
648
+ tag: 'button',
649
+ attributes: { onclick: () => color.value = 'green' },
650
+ children: ['Green']
651
+ },
652
+ {
653
+ tag: 'button',
654
+ attributes: { onclick: () => color.value = 'blue' },
655
+ children: ['Blue']
656
+ }
657
+ ]);
658
+ // Click the color buttons to change the box color
659
+ ```
660
+
661
+ ### Reactive Lists
662
+
663
+ ```javascript
664
+ // Style 2: Element Function
665
+ const { element, signal } = new Lightview();
666
+
667
+ const items = signal(['Apple', 'Banana', 'Cherry']);
668
+
669
+ const list = element('div', {}, [
670
+ () => element('ul', {},
671
+ items.value.map(item =>
672
+ element('li', {}, [item])
673
+ )
674
+ ),
675
+ element('button', {
676
+ onclick: () => items.value = [...items.value, 'New Fruit']
677
+ }, ['Add Fruit']),
678
+ element('button', {
679
+ onclick: () => items.value = items.value.slice(0, -1)
680
+ }, ['Remove Last'])
681
+ ]);
682
+ // Click buttons to add or remove items from the list
683
+ ```
684
+
685
+ ## Lifecycle & Cleanup
686
+
687
+ ### Lifecycle Hooks
688
+
689
+ Lightview provides built-in lifecycle hooks for elements, allowing you to run code when an element enters or leaves the DOM.
690
+
691
+ ```javascript
692
+ // Style 2: Element Function
693
+ const { element } = new Lightview();
694
+
695
+ /*
696
+ onmount: Called when the element is added to the DOM.
697
+ onunmount: Called when the element is removed from the DOM.
698
+ */
699
+ const timer = element('div', {
700
+ onmount: (el) => {
701
+ console.log('Timer mounted!');
702
+ el._interval = setInterval(() => console.log('Tick'), 1000);
703
+ },
704
+ onunmount: (el) => {
705
+ console.log('Timer removed!');
706
+ clearInterval(el._interval);
707
+ }
708
+ }, ['I am a timer']);
709
+ ```
710
+
711
+ These hooks are robust and triggered whether you remove elements via Lightview logic or standard DOM methods (like `element.remove()` or `innerHTML = ''`).
712
+
713
+ ### Automatic Cleanup
714
+
715
+ One of Lightview's most powerful features is its **fully automatic memory management**.
716
+
717
+ * **Self-Cleaning Effects**: When an element is removed from the DOM, Lightview automatically stops all reactive effects (signals, computed values) attached to it.
718
+ * **Leak Prevention**: You don't need to manually unsubscribe from signals. The built-in `MutationObserver` watches the document and cleans up dependencies instantly when nodes are detached.
719
+
720
+ ## Complete Examples
721
+
722
+ ### Todo App
723
+
724
+ ```javascript
725
+ const lv = new Lightview();
726
+ const { signal, tags } = lv;
727
+ const { div, h1, input, button, span } = tags;
728
+
729
+ const state = {
730
+ todos: signal([]),
731
+ input: signal(''),
732
+ filter: signal('all')
733
+ };
734
+
735
+ const addTodo = () => {
736
+ if (state.input.value.trim()) {
737
+ state.todos.value = [...state.todos.value, {
738
+ id: Date.now(),
739
+ text: signal(state.input.value),
740
+ done: signal(false)
741
+ }];
742
+ state.input.value = '';
743
+ }
744
+ };
745
+
746
+ const filteredTodos = lv.computed(() => {
747
+ const todos = state.todos.value;
748
+ const filter = state.filter.value;
749
+
750
+ if (filter === 'active') return todos.filter(t => !t.done.value);
751
+ if (filter === 'completed') return todos.filter(t => t.done.value);
752
+ return todos;
753
+ });
754
+
755
+ const app = div({ class: 'todo-app' },
756
+ h1('Lightview Todos'),
757
+
758
+ div({ class: 'input-row' },
759
+ input({
760
+ placeholder: 'What needs to be done?',
761
+ value: () => state.input.value,
762
+ oninput: (e) => state.input.value = e.target.value,
763
+ onkeypress: (e) => { if (e.key === 'Enter') addTodo(); }
764
+ }),
765
+ button({ onclick: addTodo }, 'Add')
766
+ ),
767
+
768
+ div({ class: 'filters' },
769
+ button({
770
+ class: () => state.filter.value === 'all' ? 'active' : '',
771
+ onclick: () => state.filter.value = 'all'
772
+ }, 'All'),
773
+ button({
774
+ class: () => state.filter.value === 'active' ? 'active' : '',
775
+ onclick: () => state.filter.value = 'active'
776
+ }, 'Active'),
777
+ button({
778
+ class: () => state.filter.value === 'completed' ? 'active' : '',
779
+ onclick: () => state.filter.value = 'completed'
780
+ }, 'Completed')
781
+ ),
782
+
783
+ () => div({ class: 'todo-list' },
784
+ ...filteredTodos.value.map(todo =>
785
+ div({ class: 'todo-item' },
786
+ input({
787
+ type: 'checkbox',
788
+ checked: () => todo.done.value,
789
+ onchange: () => todo.done.value = !todo.done.value
790
+ }),
791
+ span({
792
+ class: () => todo.done.value ? 'completed' : '',
793
+ ondblclick: () => {
794
+ const newText = prompt('Edit:', todo.text.value);
795
+ if (newText !== null) todo.text.value = newText;
796
+ }
797
+ }, () => todo.text.value),
798
+ button({
799
+ class: 'delete',
800
+ onclick: () => {
801
+ state.todos.value = state.todos.value.filter(t => t.id !== todo.id);
802
+ }
803
+ }, '×')
804
+ )
805
+ )
806
+ )
807
+ );
808
+
809
+ document.body.appendChild(app.domEl);
810
+ ```
811
+
812
+ ### Form with Validation
813
+
814
+ ```javascript
815
+ const lv = new Lightview();
816
+ const { signal, computed, tags } = lv;
817
+ const { form, div, label, input, span, button } = tags;
818
+
819
+ const email = signal('');
820
+ const password = signal('');
821
+
822
+ const isValidEmail = computed(() =>
823
+ /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value)
824
+ );
825
+
826
+ const isValidPassword = computed(() =>
827
+ password.value.length >= 8
828
+ );
829
+
830
+ const canSubmit = computed(() =>
831
+ isValidEmail.value && isValidPassword.value
832
+ );
833
+
834
+ const formEl = form({
835
+ onsubmit: (e) => {
836
+ e.preventDefault();
837
+ if (canSubmit.value) { // or canSubmit()
838
+ alert('Form submitted!');
839
+ }
840
+ }
841
+ },
842
+ div(
843
+ label('Email:'),
844
+ input({
845
+ type: 'email',
846
+ value: () => email.value,
847
+ oninput: (e) => email.value = e.target.value,
848
+ class: () => email.value && !isValidEmail.value ? 'invalid' : ''
849
+ }),
850
+ () => email.value && !isValidEmail.value
851
+ ? span({ class: 'error' }, 'Invalid email')
852
+ : span()
853
+ ),
854
+
855
+ div(
856
+ label('Password:'),
857
+ input({
858
+ type: 'password',
859
+ value: () => password.value,
860
+ oninput: (e) => password.value = e.target.value,
861
+ class: () => password.value && !isValidPassword.value ? 'invalid' : ''
862
+ }),
863
+ () => password.value && !isValidPassword.value
864
+ ? span({ class: 'error' }, 'Must be 8+ characters')
865
+ : span()
866
+ ),
867
+
868
+ button({
869
+ type: 'submit',
870
+ disabled: () => !canSubmit.value
871
+ }, 'Submit')
872
+ );
873
+
874
+ document.body.appendChild(formEl.domEl);
875
+ ```
876
+
877
+ ### Dynamic Chart
878
+
879
+ ```javascript
880
+ const lv = new Lightview();
881
+ const { signal, tags } = lv;
882
+ const { div, button } = tags;
883
+
884
+ const data = signal([10, 20, 15, 30, 25]);
885
+
886
+ const addDataPoint = () => {
887
+ data.value = [...data.value, Math.floor(Math.random() * 50)];
888
+ };
889
+
890
+ const chart = div({ class: 'chart' },
891
+ button({ onclick: addDataPoint }, 'Add Data Point'),
892
+
893
+ () => div({ class: 'bars' },
894
+ ...data.value.map((value, i) =>
895
+ div({
896
+ class: 'bar',
897
+ style: () => ({
898
+ height: `${value * 3}px`,
899
+ width: '40px',
900
+ backgroundColor: `hsl(${i * 40}, 70%, 50%)`,
901
+ display: 'inline-block',
902
+ margin: '0 2px',
903
+ transition: 'height 0.3s'
904
+ })
905
+ }, value.toString())
906
+ )
907
+ )
908
+ );
909
+
910
+ document.body.appendChild(chart.domEl);
911
+ ```
912
+
913
+ ## Hypermedia
914
+
915
+ Lightview enhances the use of 'href' and 'src' across any element.
916
+
917
+ ## Smart `src` Attribute
918
+
919
+ Lightview enhances the `src` attribute with smart loading capabilities. You can use it to inject content from external files or other parts of the DOM.
920
+
921
+ ### 1. Fetching Content (HTML/JSON)
922
+ If `src` is a file path, Lightview fetches it:
923
+
924
+ ```javascript
925
+ // Style 2: Element Function
926
+ const { element } = new Lightview();
927
+
928
+ // Fetches header.html and parses it into reactive elements
929
+ element('div', { src: '/components/header.html' }, [])
930
+
931
+ // Fetches data.json and converts it to elements
932
+ element('div', { src: '/api/data.json' }, [])
933
+ ```
934
+
935
+ ### 2. DOM Cloning
936
+ If `src` is a CSS selector, Lightview clones the targeted DOM elements:
937
+
938
+ ```javascript
939
+ // Style 3: Plain JSON
940
+ const { element } = new Lightview();
941
+
942
+ // Clones the element with id="template-sidebar"
943
+ element('div', {}, [
944
+ {
945
+ tag: 'div',
946
+ attributes: { src: '#template-sidebar' },
947
+ children: []
948
+ }
949
+ ])
950
+ ```
951
+
952
+ This is useful for using hidden templates or duplicating content.
953
+
954
+ ### 3. Interactive `href` Attribute
955
+ On elements (like `div`, `button`, etc.), a non-standard `href` attribute acts as a click trigger for the `src` behavior. When the element is clicked, its `src` attribute is set to the value of its `href`, triggering the content loading or cloning.
956
+
957
+ ```javascript
958
+ // Style 1: Tagged API
959
+ const { tags } = new Lightview();
960
+ const { div, button } = tags;
961
+
962
+ // On click, this div will load 'content.html' into itself
963
+ div({ href: 'content.html' }, 'Click to load content')
964
+
965
+ // On click, this button will clone #modal-template into itself
966
+ div({ href: '#modal-template' }, 'Open Modal')
967
+ ```
968
+
969
+ ## HTML Template Literals
970
+
971
+ Lightview-X supports reactive template literals in external HTML and JSON files. This allows you to create reusable templates that automatically bind to your global signals and state objects using standard JavaScript template literal syntax.
972
+
973
+ **Note:** This feature requires `lightview-x.js` and uses named signals/state registered globally.
974
+
975
+ ### Template Literals in HTML Files
976
+
977
+ Create HTML templates with `${...}` expressions that reference named signals:
978
+
979
+ **template.html:**
980
+ ```html
981
+ <div class="card">
982
+ <h3>Welcome, User!</h3>
983
+ <p>Your status: <strong>${signal.get('userStatus').value}</strong></p>
984
+ <p>Messages: <span>${signal.get('messageCount').value}</span></p>
985
+ <p>Last updated: ${new Date().toLocaleTimeString()}</p>
986
+ </div>
987
+ ```
988
+
989
+ **main.js:**
990
+ ```javascript
991
+ const { signal, tags } = Lightview;
992
+ const { section, button } = tags;
993
+
994
+ // Register named signals that the template will reference
995
+ const userStatus = signal('Online', 'userStatus');
996
+ const messageCount = signal(5, 'messageCount');
997
+
998
+ // Load and render the template
999
+ const app = section({ src: './template.html' });
1000
+
1001
+ // Update signals - reload template to see changes
1002
+ userStatus.value = 'Away';
1003
+ messageCount.value++;
1004
+ ```
1005
+
1006
+ ### Template Literals in JSON Files
1007
+
1008
+ JSON templates use the same `${...}` syntax within string values to access registered state, provide defaults, or do calculations and conditional logic. You can load these with the 'src' attribute on any element.
1009
+
1010
+ #### Supported JSON Formats
1011
+
1012
+ JSON files loaded via `src` support both **vDOM** and **Object DOM** formats:
1013
+
1014
+ | Format | Structure | Requirement |
1015
+ |--------|-----------|-------------|
1016
+ | **vDOM** | `{ "tag": "div", "attributes": {...}, "children": [...] }` | Works by default |
1017
+ | **Object DOM** | `{ "div": { "class": "foo", "children": [...] } }` | Requires `LightviewX.useObjectDOMSyntax()` |
1018
+
1019
+ #### vDOM Format (Default)
1020
+
1021
+ **template.json:**
1022
+ ```json
1023
+ [
1024
+ {
1025
+ "tag": "div",
1026
+ "attributes": { "class": "card" },
1027
+ "children": [
1028
+ {
1029
+ "tag": "h3",
1030
+ "children": ["Product: ${state.get('product',{name:'Widget One',price:0,inStock:3}).name}"]
1031
+ },
1032
+ {
1033
+ "tag": "p",
1034
+ "children": ["Price: $${state.get('product').price}"]
1035
+ },
1036
+ {
1037
+ "tag": "p",
1038
+ "children": ["In Stock: ${state.get('product').inStock ? 'Yes ✅' : 'No ❌'}"]
1039
+ }
1040
+ ]
1041
+ }
1042
+ ]
1043
+ ```
1044
+
1045
+ #### Object DOM Format
1046
+
1047
+ To use the more compact Object DOM format in JSON files, enable it before loading:
1048
+
1049
+ **template-objectdom.json:**
1050
+ ```json
1051
+ [
1052
+ {
1053
+ "div": {
1054
+ "class": "card",
1055
+ "children": [
1056
+ { "h3": { "children": ["Product: ${state.get('product').name}"] } },
1057
+ { "p": { "children": ["Price: $${state.get('product').price}"] } },
1058
+ { "p": { "children": ["In Stock: ${state.get('product').inStock ? 'Yes ✅' : 'No ❌'}"] } }
1059
+ ]
1060
+ }
1061
+ }
1062
+ ]
1063
+ ```
1064
+
1065
+ **main.js:**
1066
+ ```javascript
1067
+ // Enable Object DOM syntax BEFORE loading JSON templates
1068
+ LightviewX.useObjectDOMSyntax();
1069
+
1070
+ const { state, tags } = Lightview;
1071
+ const { section } = tags;
1072
+
1073
+ const product = state({ name: 'Widget', price: 29.99, inStock: true }, 'product');
1074
+
1075
+ // Now JSON files can use Object DOM format
1076
+ const app = section({ src: './template-objectdom.json' });
1077
+ ```
1078
+
1079
+ **main.js:**
1080
+ ```javascript
1081
+ const { state, tags } = Lightview;
1082
+ const { section, button } = tags;
1083
+
1084
+ // Register named state that the template will reference
1085
+ const product = state({
1086
+ name: 'Lightview Widget',
1087
+ price: 29.99,
1088
+ inStock: true
1089
+ }, 'product');
1090
+
1091
+ // Load and render the JSON template
1092
+ const app = section({ src: './template.json' });
1093
+
1094
+ // Update state values
1095
+ product.name = 'Super Widget';
1096
+ product.price = 39.99;
1097
+ ```
1098
+
1099
+ ### Reloading Templates
1100
+
1101
+ To see updated values after changing signals/state, reload the template by re-setting the `src` attribute:
1102
+
1103
+ ```javascript
1104
+ const { signal, state, tags } = Lightview;
1105
+ const { section, button, div } = tags;
1106
+
1107
+ // Named signal and state
1108
+ const counter = signal(0, 'counter');
1109
+ const user = state({ name: 'Alice' }, 'user');
1110
+
1111
+ const app = div(
1112
+ section({ src: './dashboard.html', id: 'dashboard' }),
1113
+
1114
+ button({ onclick: () => counter.value++ }, 'Increment'),
1115
+ button({ onclick: () => user.name = 'Bob' }, 'Change User'),
1116
+
1117
+ button({ onclick: () => {
1118
+ // Reload template to reflect updated values
1119
+ const container = document.getElementById('dashboard');
1120
+ const el = Lightview.internals.domToElement.get(container);
1121
+ if (el) {
1122
+ el.attributes = { ...el.attributes, src: './dashboard.html?' + Date.now() };
1123
+ }
1124
+ }}, 'Reload Template')
1125
+ );
1126
+ ```
1127
+
1128
+ ### Why Use Template Literals?
1129
+
1130
+ - **Separation of Concerns**: Keep HTML structure in `.html` files, logic in `.js` files
1131
+ - **Reusable Templates**: Share templates across different parts of your application
1132
+ - **Server-Side Templates**: Generate templates on the server with dynamic `${...}` expressions
1133
+ - **CMS Integration**: Non-developers can edit HTML templates without touching JavaScript
1134
+
1135
+ ## Enhancing Existing DOM Elements
1136
+
1137
+ Lightview-X provides the `enhance()` function to add reactivity to existing DOM elements. This is useful for progressive enhancement - adding interactivity to server-rendered HTML or gradually migrating a codebase without rebuilding the entire page.
1138
+
1139
+ **Note:** This feature requires `lightview-x.js`.
1140
+
1141
+ ### Basic Usage
1142
+
1143
+ ```html
1144
+ <!-- Existing HTML (server-rendered, static HTML, etc.) -->
1145
+ <button id="counter-btn" class="btn">Clicked 0 times</button>
1146
+ <div id="status-display">Status: Unknown</div>
1147
+ ```
1148
+
1149
+ ```javascript
1150
+ const { signal, state } = Lightview;
1151
+
1152
+ // Get or create a named signal with default value
1153
+ const counter = signal.get('counter', 0);
1154
+
1155
+ // Enhance the button with reactivity
1156
+ LightviewX.enhance('#counter-btn', {
1157
+ innerText: () => `Clicked ${counter.value} times`,
1158
+ onclick: () => counter.value++
1159
+ });
1160
+
1161
+ // Enhance with innerHTML for richer content
1162
+ LightviewX.enhance('#status-display', {
1163
+ innerHTML: () => `Status: <strong>${counter.value > 5 ? 'Active' : 'Idle'}</strong>`
1164
+ });
1165
+ ```
1166
+
1167
+ ### API
1168
+
1169
+ ```javascript
1170
+ LightviewX.enhance(selectorOrNode, options)
1171
+ ```
1172
+
1173
+ **Parameters:**
1174
+ - `selectorOrNode` - CSS selector string or DOM element
1175
+ - `options` - Object containing:
1176
+ - `innerText` - Static string or reactive function for text content
1177
+ - `innerHTML` - Static string or reactive function for HTML content
1178
+ - `on*` - Event handlers (`onclick`, `oninput`, etc.)
1179
+ - Any other attribute (reactive functions supported)
1180
+
1181
+ **Returns:** Lightview reactive element wrapper, or `null` if element not found.
1182
+
1183
+ ### signal.get() and state.get() with Defaults
1184
+
1185
+ When using `enhance()`, you often need to access or create global state. The `.get()` method now supports a default value that creates and registers the signal/state if it doesn't exist:
1186
+
1187
+ ```javascript
1188
+ // If 'counter' doesn't exist, creates signal(0) and registers it as 'counter'
1189
+ const counter = signal.get('counter', 0);
1190
+
1191
+ // If 'user' doesn't exist, creates state({name: 'Guest'}) and registers it as 'user'
1192
+ const user = state.get('user', { name: 'Guest' });
1193
+
1194
+ // Without default - returns undefined if not registered
1195
+ const maybeCounter = signal.get('counter');
1196
+ ```
1197
+
1198
+ This pattern is similar to `getOrCreate` - the default value is only used if the signal/state hasn't been registered yet.
1199
+
1200
+ ### Complete Example
1201
+
1202
+ ```html
1203
+ <!DOCTYPE html>
1204
+ <html>
1205
+ <body>
1206
+ <!-- Server-rendered content -->
1207
+ <div class="card">
1208
+ <h2 id="greeting">Hello, Guest!</h2>
1209
+ <p id="click-count">Clicks: 0</p>
1210
+ <button id="increment">Click Me</button>
1211
+ <button id="change-name">Change Name</button>
1212
+ </div>
1213
+
1214
+ <script src="lightview.js"></script>
1215
+ <script src="lightview-x.js"></script>
1216
+ <script>
1217
+ const { signal, state } = Lightview;
1218
+
1219
+ // Create or get global state with defaults
1220
+ const clicks = signal.get('clicks', 0);
1221
+ const user = state.get('user', { name: 'Guest' });
1222
+
1223
+ // Enhance existing elements
1224
+ LightviewX.enhance('#greeting', {
1225
+ innerText: () => `Hello, ${user.name}!`
1226
+ });
1227
+
1228
+ LightviewX.enhance('#click-count', {
1229
+ innerText: () => `Clicks: ${clicks.value}`
1230
+ });
1231
+
1232
+ LightviewX.enhance('#increment', {
1233
+ onclick: () => clicks.value++
1234
+ });
1235
+
1236
+ LightviewX.enhance('#change-name', {
1237
+ onclick: () => {
1238
+ user.name = user.name === 'Guest' ? 'Alice' : 'Guest';
1239
+ }
1240
+ });
1241
+ </script>
1242
+ </body>
1243
+ </html>
1244
+ ```
1245
+
1246
+ ### When to Use enhance()
1247
+
1248
+ - **Progressive Enhancement**: Add interactivity to static HTML
1249
+ - **Server-Side Rendering**: Hydrate server-rendered content
1250
+ - **Legacy Integration**: Add reactivity to existing applications
1251
+ - **CMS Content**: Enhance content from a CMS without rebuilding
1252
+ - **Third-Party Widgets**: Add reactive behavior to elements you don't control
1253
+
1254
+ ### enhance() vs element()
1255
+
1256
+ | Use Case | Use `enhance()` | Use `element()` |
1257
+ |----------|-----------------|------------------|
1258
+ | Existing HTML | ✅ | ❌ |
1259
+ | Build from scratch | ❌ | ✅ |
1260
+ | Server-rendered | ✅ | ❌ |
1261
+ | Full control over structure | ❌ | ✅ |
1262
+ | Progressive enhancement | ✅ | ❌ |
1263
+
1264
+
1265
+ ## Framework Comparison
1266
+
1267
+ Lightview offers a unique combination of features that sets it apart from other reactive libraries. Here's how it compares:
1268
+
1269
+ ### Features
1270
+
1271
+ | Feature | Lightview | Vue 3 | SolidJS | Svelte | Bau.js | Juris.js |
1272
+ |---------|-----------|-------|---------|--------|--------|----------|
1273
+ | **Bundle Size** | ~6.5KB | ~33KB | ~7KB | ~2KB* | ~2KB | ~50KB |
1274
+ | **Reactivity Model** | Signals/Proxy | Proxy | Signals | Compiler | Proxy | Intentional |
1275
+ | **No Build Required** | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ |
1276
+ | **No JSX/Templates** | ✅ | ❌ (SFC) | ❌ (JSX) | ❌ (Templates) | ✅ | ✅ |
1277
+ | **Tagged API** | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
1278
+ | **Plain Objects** | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |
1279
+ | **Hypermedia** *| ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
1280
+ | **HTML Template Literals** *| ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
1281
+
1282
+ *Svelte size is after compilation. However, substantive Svelete apps are often larger than those created with other libraries due to the lack of rutime abstractions after compilation/transpilation.
1283
+
1284
+ *Lighview Hypermedia and HTML Template Literals require lightview-x, an additional 5K.
1285
+
1286
+ ### Reactive Capabilities
1287
+
1288
+ | Feature | Lightview | Vue 3 | SolidJS | Svelte | Bau.js | Juris.js |
1289
+ |---------|-----------|-------|---------|--------|--------|----------|
1290
+ | **Array Mutations** | ✅ Auto | ✅ Auto | ❌ Manual** | ✅ Auto | ✅ Auto | ❌ Manual |
1291
+ | **Array Element Mutations** | ✅ **Auto** | ❌ Manual | ❌ Manual | ❌ Manual | ❌ Manual | ❌ Manual |
1292
+ | **Date Mutations** | ✅ **Auto** | ❌ Manual | ❌ Manual | ❌ Manual | ❌ Manual | ❌ Manual |
1293
+ | **Deep Reactivity** | ✅ | ✅ | ✅*** | ✅ | ✅ | ✅ |
1294
+ | **Computed Values** | ✅ | ✅ | ✅ | ✅ | ✅ (derive) | ✅ |
1295
+ | **Ehancing Existing HTML** | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |
1296
+ | **Fine-grained Updates** | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ |
1297
+ | **Auto Cleanup** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
1298
+
1299
+ **SolidJS supports array mutations with `createStore`, but not with `createSignal`
1300
+ ***SolidJS requires `createStore` for deep reactivity
1301
+
1302
+ ### Developer Experience
1303
+
1304
+ | Feature | Lightview | Vue 3 | SolidJS | Svelte | Bau.js | Juris.js |
1305
+ |---------|-----------|-------|---------|--------|--------|----------|
1306
+ | **Learning Curve** | Low | Medium | Medium | Low | Low | Medium |
1307
+ | **TypeScript** | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ |
1308
+ | **DevTools** | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
1309
+ | **Lifecycle Hooks** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
1310
+ | **Progressive Enhancement** | ✅ (src/href) | ❌ | ❌ | ❌ | ❌ | ✅ (enhance) |
1311
+ | **Multiple Instances** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
1312
+
1313
+ ### Unique Features
1314
+
1315
+ **Lightview's Standout Features:**
1316
+ - 🎯 **Reactive Date Objects** - Only library with automatic Date mutation tracking
1317
+ - 📊 **Reactive Array Elements** - Full deep reactivity for array elements (rare feature!)
1318
+ - 🏷️ **Tagged API** - Bau.js-inspired concise syntax (`tags.div()`)
1319
+ - 🔗 **Smart src/href** - Load HTML/JSON or clone DOM elements declaratively
1320
+ - 📝 **HTML Template Literals** - Use `${signal.get('name').value}` in external HTML/JSON files
1321
+ - 🔧 **Progressive Enhancement** - Enhance existing DOM with `enhance()` for server-rendered content
1322
+ - 🧩 **Component Functions** - Use functions as tags, with registry and global scope support
1323
+ - 🧹 **Automatic Cleanup** - MutationObserver-based memory management
1324
+ - 📦 **Zero Dependencies** - Pure JavaScript, no build tools needed
1325
+ - 🎨 **Five API Styles** - Tagged, Element function, vDOM JSON, Object DOM JSON, Component functions
1326
+
1327
+ **When to Choose Lightview:**
1328
+ - ✅ You want minimal bundle size with maximum features
1329
+ - ✅ You need reactive Date objects (calendars, timers, scheduling apps)
1330
+ - ✅ You prefer no build step and pure JavaScript
1331
+ - ✅ You like the simplicity of Bau.js but want more power
1332
+ - ✅ You're building hypermedia-driven applications
1333
+
1334
+ **When to Choose Alternatives:**
1335
+ - Vue 3: Large ecosystem, TypeScript, mature tooling
1336
+ - SolidJS: Maximum performance, fine-grained reactivity
1337
+ - Svelte: Best DX, compiler optimizations
1338
+ - Bau.js: Even simpler API, minimal features
1339
+ - Juris.js: Object-first architecture, intentional reactivity
1340
+
1341
+ ## Browser Support
1342
+
1343
+
1344
+ Modern browsers with Proxy support (ES6+):
1345
+ - Chrome 49+
1346
+ - Firefox 18+
1347
+ - Safari 10+
1348
+ - Edge 12+
1349
+
1350
+ ## License
1351
+
1352
+ MIT