lightview 1.8.2 → 2.0.0

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 (264) hide show
  1. package/.agent/workflows/daisyui-component-migration.md +155 -0
  2. package/.codacy/cli.sh +149 -0
  3. package/.codacy/codacy.yaml +15 -0
  4. package/.github/instructions/codacy.instructions.md +72 -0
  5. package/.wranglerignore +21 -0
  6. package/README.md +1330 -19
  7. package/_headers +4 -0
  8. package/build.js +70 -0
  9. package/components/actions/button.js +151 -0
  10. package/components/actions/dropdown.js +120 -0
  11. package/components/actions/modal.js +146 -0
  12. package/components/actions/swap.js +118 -0
  13. package/components/daisyui.js +288 -0
  14. package/components/data-display/accordion.js +128 -0
  15. package/components/data-display/alert.js +112 -0
  16. package/components/data-display/avatar.js +170 -0
  17. package/components/data-display/badge.js +82 -0
  18. package/components/data-display/card.js +151 -0
  19. package/components/data-display/carousel.js +94 -0
  20. package/components/data-display/chart.js +220 -0
  21. package/components/data-display/chat.js +128 -0
  22. package/components/data-display/collapse.js +103 -0
  23. package/components/data-display/countdown.js +69 -0
  24. package/components/data-display/diff.js +111 -0
  25. package/components/data-display/kbd.js +65 -0
  26. package/components/data-display/loading.js +75 -0
  27. package/components/data-display/progress.js +79 -0
  28. package/components/data-display/radial-progress.js +88 -0
  29. package/components/data-display/skeleton.js +66 -0
  30. package/components/data-display/stats.js +159 -0
  31. package/components/data-display/table.js +146 -0
  32. package/components/data-display/timeline.js +146 -0
  33. package/components/data-display/toast.js +72 -0
  34. package/components/data-display/tooltip.js +74 -0
  35. package/components/data-input/checkbox.js +253 -0
  36. package/components/data-input/file-input.js +224 -0
  37. package/components/data-input/input.js +264 -0
  38. package/components/data-input/radio.js +338 -0
  39. package/components/data-input/range.js +204 -0
  40. package/components/data-input/rating.js +219 -0
  41. package/components/data-input/select.js +287 -0
  42. package/components/data-input/textarea.js +287 -0
  43. package/components/data-input/toggle.js +201 -0
  44. package/components/index.js +137 -0
  45. package/components/layout/divider.js +72 -0
  46. package/components/layout/drawer.js +142 -0
  47. package/components/layout/footer.js +100 -0
  48. package/components/layout/hero.js +109 -0
  49. package/components/layout/indicator.js +90 -0
  50. package/components/layout/join.js +78 -0
  51. package/components/layout/navbar.js +110 -0
  52. package/components/navigation/breadcrumbs.js +91 -0
  53. package/components/navigation/dock.js +103 -0
  54. package/components/navigation/menu.js +126 -0
  55. package/components/navigation/pagination.js +105 -0
  56. package/components/navigation/steps.js +89 -0
  57. package/components/navigation/tabs.css +177 -0
  58. package/components/navigation/tabs.js +123 -0
  59. package/components/theme/theme-switch.css +65 -0
  60. package/components/theme/theme-switch.js +177 -0
  61. package/docs/about.html +164 -0
  62. package/docs/api/computed.html +184 -0
  63. package/docs/api/effects.html +173 -0
  64. package/docs/api/elements.html +180 -0
  65. package/docs/api/enhance.html +225 -0
  66. package/docs/api/hypermedia.html +165 -0
  67. package/docs/api/index.html +178 -0
  68. package/docs/api/nav.html +18 -0
  69. package/docs/api/signals.html +136 -0
  70. package/docs/api/state.html +217 -0
  71. package/docs/assets/images/logo-favicon.svg +42 -0
  72. package/docs/assets/images/logo-static.svg +40 -0
  73. package/docs/assets/images/logo.svg +66 -0
  74. package/docs/assets/js/examplify.js +395 -0
  75. package/docs/assets/styles/site.css +1102 -0
  76. package/docs/assets/styles/themes.css +236 -0
  77. package/docs/components/accordion.html +439 -0
  78. package/docs/components/alert.html +528 -0
  79. package/docs/components/avatar.html +586 -0
  80. package/docs/components/badge.html +531 -0
  81. package/docs/components/breadcrumbs.html +278 -0
  82. package/docs/components/button.html +579 -0
  83. package/docs/components/card.html +561 -0
  84. package/docs/components/carousel.html +286 -0
  85. package/docs/components/chart-area.html +702 -0
  86. package/docs/components/chart-bar.html +782 -0
  87. package/docs/components/chart-column.html +735 -0
  88. package/docs/components/chart-line.html +794 -0
  89. package/docs/components/chart-pie.html +823 -0
  90. package/docs/components/chart.html +610 -15
  91. package/docs/components/chat.html +547 -0
  92. package/docs/components/checkbox.html +641 -0
  93. package/docs/components/collapse.html +536 -0
  94. package/docs/components/component-nav.html +53 -0
  95. package/docs/components/countdown.html +470 -0
  96. package/docs/components/diff.html +245 -0
  97. package/docs/components/divider.html +240 -0
  98. package/docs/components/dock.html +277 -0
  99. package/docs/components/drawer.html +515 -0
  100. package/docs/components/dropdown.html +479 -0
  101. package/docs/components/file-input.html +591 -0
  102. package/docs/components/footer.html +301 -0
  103. package/docs/components/gallery.html +504 -0
  104. package/docs/components/hero.html +264 -0
  105. package/docs/components/index.css +840 -0
  106. package/docs/components/index.html +735 -0
  107. package/docs/components/indicator.html +342 -0
  108. package/docs/components/input.html +644 -0
  109. package/docs/components/join.html +285 -0
  110. package/docs/components/kbd.html +322 -0
  111. package/docs/components/loading.html +521 -0
  112. package/docs/components/menu.html +461 -0
  113. package/docs/components/modal.html +639 -0
  114. package/docs/components/navbar.html +321 -0
  115. package/docs/components/pagination.html +279 -0
  116. package/docs/components/progress.html +514 -0
  117. package/docs/components/radial-progress.html +434 -0
  118. package/docs/components/radio.html +655 -0
  119. package/docs/components/range.html +611 -0
  120. package/docs/components/rating.html +642 -0
  121. package/docs/components/select.html +696 -0
  122. package/docs/components/sidebar-setup.js +93 -0
  123. package/docs/components/skeleton.html +447 -0
  124. package/docs/components/spinner.html +68 -0
  125. package/docs/components/stats.html +486 -0
  126. package/docs/components/steps.html +356 -0
  127. package/docs/components/swap.html +517 -0
  128. package/docs/components/switch.html +68 -0
  129. package/docs/components/table.html +668 -0
  130. package/docs/components/tabs.html +506 -0
  131. package/docs/components/text-input.html +68 -0
  132. package/docs/components/textarea.html +603 -0
  133. package/docs/components/timeline.html +485 -42
  134. package/docs/components/toast.html +474 -0
  135. package/docs/components/toggle.html +564 -0
  136. package/docs/components/tooltip.html +423 -0
  137. package/docs/examples/getting-started-example.html +40 -0
  138. package/docs/examples/index.html +93 -0
  139. package/docs/getting-started/index.html +739 -0
  140. package/docs/getting-started/reviews.html +23 -0
  141. package/docs/getting-started/reviews.odom +108 -0
  142. package/docs/getting-started/reviews.vdom +84 -0
  143. package/docs/index.html +132 -42
  144. package/docs/playground.html +416 -0
  145. package/docs/router.html +285 -0
  146. package/docs/styles/index.html +190 -0
  147. package/functions/_middleware.js +32 -0
  148. package/index.html +309 -0
  149. package/lightview-router.js +364 -0
  150. package/lightview-x.js +1577 -0
  151. package/lightview.js +659 -1200
  152. package/lightview.js.backup +793 -0
  153. package/middleware/locale.js +25 -0
  154. package/middleware/markdown.js +44 -0
  155. package/middleware/notFound.js +37 -0
  156. package/package.json +27 -41
  157. package/watch.js +92 -0
  158. package/wrangler.toml +12 -0
  159. package/.idea/lightview.iml +0 -12
  160. package/.idea/modules.xml +0 -8
  161. package/.idea/vcs.xml +0 -6
  162. package/LICENSE +0 -21
  163. package/codepen-no-tabs-embed.css +0 -2
  164. package/docs/CNAME +0 -1
  165. package/docs/api.html +0 -674
  166. package/docs/blank.html +0 -10
  167. package/docs/comparedto.html +0 -89
  168. package/docs/components/chart-repl.html +0 -69
  169. package/docs/components/components.js +0 -113
  170. package/docs/components/contents.html +0 -17
  171. package/docs/components/gantt-repl.html +0 -61
  172. package/docs/components/gantt.html +0 -42
  173. package/docs/components/gauge-repl.html +0 -66
  174. package/docs/components/gauge.html +0 -20
  175. package/docs/components/orgchart-repl.html +0 -64
  176. package/docs/components/orgchart.html +0 -41
  177. package/docs/components/repl-as-src.html +0 -17
  178. package/docs/components/repl-repl.html +0 -95
  179. package/docs/components/repl.html +0 -527
  180. package/docs/components/timeline-repl.html +0 -72
  181. package/docs/components.html +0 -14
  182. package/docs/css/highlightjs.min.css +0 -9
  183. package/docs/css/tutorial.css +0 -35
  184. package/docs/examples/anchor.html +0 -11
  185. package/docs/examples/chart.html +0 -34
  186. package/docs/examples/counter.html +0 -26
  187. package/docs/examples/counter.test.mjs +0 -47
  188. package/docs/examples/counter2.html +0 -26
  189. package/docs/examples/directives.html +0 -79
  190. package/docs/examples/foreign.html +0 -50
  191. package/docs/examples/forgeinform.html +0 -98
  192. package/docs/examples/form.html +0 -61
  193. package/docs/examples/gauge.html +0 -18
  194. package/docs/examples/invalid-template-literals.html +0 -44
  195. package/docs/examples/medium/remote.html +0 -60
  196. package/docs/examples/message.html +0 -18
  197. package/docs/examples/nested.html +0 -11
  198. package/docs/examples/object-bound-form.html +0 -34
  199. package/docs/examples/remote-server.js +0 -51
  200. package/docs/examples/remote.html +0 -34
  201. package/docs/examples/remote.json +0 -1
  202. package/docs/examples/scratch.html +0 -69
  203. package/docs/examples/sensors/index.html +0 -44
  204. package/docs/examples/sensors/sensor-server.js +0 -30
  205. package/docs/examples/shared.html +0 -41
  206. package/docs/examples/template.html +0 -33
  207. package/docs/examples/timeline.html +0 -21
  208. package/docs/examples/todo.html +0 -40
  209. package/docs/examples/top.html +0 -10
  210. package/docs/examples/types.html +0 -94
  211. package/docs/examples/xor.html +0 -62
  212. package/docs/examples.html +0 -25
  213. package/docs/javascript/codejar.min.js +0 -8
  214. package/docs/javascript/highlightjs.min.js +0 -1173
  215. package/docs/javascript/isomorphic-git.js +0 -9
  216. package/docs/javascript/json5.min.js +0 -1
  217. package/docs/javascript/lightning-fs.js +0 -1
  218. package/docs/javascript/lightview.js +0 -1285
  219. package/docs/javascript/marked.min.js +0 -6
  220. package/docs/javascript/peerjs.min.js +0 -70
  221. package/docs/javascript/turndown.js +0 -973
  222. package/docs/javascript/types.js +0 -606
  223. package/docs/javascript/utils.js +0 -45
  224. package/docs/lightview.html +0 -63
  225. package/docs/old_index.html +0 -965
  226. package/docs/old_index.md +0 -1132
  227. package/docs/slidein.html +0 -51
  228. package/docs/tutorial/0-getting-started.html +0 -67
  229. package/docs/tutorial/1-intro-to-variables.html +0 -103
  230. package/docs/tutorial/10-template-components.html +0 -80
  231. package/docs/tutorial/11-linked-components.html +0 -76
  232. package/docs/tutorial/12-imported-components.html +0 -67
  233. package/docs/tutorial/13-input-binding.html +0 -94
  234. package/docs/tutorial/14-automatic-variable-creation.html +0 -74
  235. package/docs/tutorial/15-form-binding.html +0 -110
  236. package/docs/tutorial/16-if-directive.html +0 -60
  237. package/docs/tutorial/17-loop-directives.html +0 -83
  238. package/docs/tutorial/18-sanitizing-and-escaping-input.html +0 -79
  239. package/docs/tutorial/2-imported-and-exported-variables.html +0 -80
  240. package/docs/tutorial/3-data-types.html +0 -89
  241. package/docs/tutorial/4-extended-data-types.html +0 -83
  242. package/docs/tutorial/5-extended-functional-types.html +0 -96
  243. package/docs/tutorial/5.1-extended-functional-types.html +0 -79
  244. package/docs/tutorial/5.2-extended-functional-types.html +0 -70
  245. package/docs/tutorial/6-conventional-javascript.html +0 -75
  246. package/docs/tutorial/7-monitoring-with-observers.html +0 -107
  247. package/docs/tutorial/8-event-listeners.html +0 -65
  248. package/docs/tutorial/9-intro-to-components.html +0 -91
  249. package/docs/tutorial/contents.html +0 -32
  250. package/docs/tutorial/my-component.html +0 -29
  251. package/docs/tutorial/remote-value.json +0 -4
  252. package/docs/websiterepl.html +0 -46
  253. package/jest-puppeteer.config.js +0 -5
  254. package/jest.config.json +0 -12
  255. package/lightview.min.js +0 -1
  256. package/lightview_good.js +0 -1267
  257. package/lightview_optimized.js +0 -1274
  258. package/repl_hold.html +0 -320
  259. package/test/basic.html +0 -104
  260. package/test/basic.test.mjs +0 -315
  261. package/test/extended.html +0 -29
  262. package/test/extended.test.mjs +0 -448
  263. package/types.js +0 -607
  264. package/unsplash.key +0 -1
@@ -0,0 +1,793 @@
1
+ (() => {
2
+ // ============= SIGNALS =============
3
+
4
+ let currentEffect = null;
5
+
6
+
7
+ const getOrSet = (map, key, factory) => {
8
+ let v = map.get(key);
9
+ if (!v) {
10
+ v = factory();
11
+ map.set(key, v);
12
+ }
13
+ return v;
14
+ };
15
+
16
+ const nodeState = new WeakMap();
17
+ const nodeStateFactory = () => ({ effects: [], onmount: null, onunmount: null });
18
+
19
+ const signalRegistry = new Map();
20
+
21
+ // Helper to attach .for() method to arrays for list reconciliation
22
+ const enhanceArray = (arr) => {
23
+ if (!Array.isArray(arr)) return;
24
+ // Avoid redefining if already present
25
+ if (arr.hasOwnProperty('for')) return;
26
+ Object.defineProperty(arr, 'for', {
27
+ configurable: true,
28
+ enumerable: false,
29
+ value: function (fn) {
30
+ return { [LIST_MARKER]: true, items: this, fn };
31
+ }
32
+ });
33
+ };
34
+
35
+ const signal = (initialValue, name) => {
36
+ enhanceArray(initialValue);
37
+ let value = initialValue;
38
+ const subscribers = new Set();
39
+
40
+ const f = (...args) => {
41
+ if (args.length === 0) return f.value;
42
+ f.value = args[0];
43
+ };
44
+
45
+ Object.defineProperty(f, 'value', {
46
+ get() {
47
+ if (currentEffect) {
48
+ subscribers.add(currentEffect);
49
+ currentEffect.dependencies.add(subscribers);
50
+ }
51
+ return value;
52
+ },
53
+ set(newValue) {
54
+ if (value !== newValue) {
55
+ enhanceArray(newValue);
56
+ value = newValue;
57
+ // Copy subscribers to avoid infinite loop when effect re-subscribes during iteration
58
+ [...subscribers].forEach(effect => effect());
59
+ }
60
+ }
61
+ });
62
+
63
+ if (name) {
64
+ signalRegistry.set(name, f);
65
+ }
66
+
67
+ return f;
68
+ };
69
+
70
+ signal.get = (name, defaultValue) => {
71
+ if (!signalRegistry.has(name) && defaultValue !== undefined) {
72
+ return signal(defaultValue, name);
73
+ }
74
+ return signalRegistry.get(name);
75
+ };
76
+
77
+ const effect = (fn) => {
78
+ const execute = () => {
79
+ if (!execute.active) return;
80
+ // Cleanup old dependencies
81
+ execute.dependencies.forEach(dep => dep.delete(execute));
82
+ execute.dependencies.clear();
83
+
84
+ currentEffect = execute;
85
+ fn();
86
+ currentEffect = null;
87
+ };
88
+
89
+ execute.active = true;
90
+ execute.dependencies = new Set();
91
+ execute.stop = () => {
92
+ execute.dependencies.forEach(dep => dep.delete(execute));
93
+ execute.dependencies.clear();
94
+ execute.active = false;
95
+ };
96
+ execute();
97
+ return execute;
98
+ };
99
+
100
+ const trackEffect = (node, effectFn) => {
101
+ const state = getOrSet(nodeState, node, nodeStateFactory);
102
+ if (!state.effects) state.effects = [];
103
+ state.effects.push(effectFn);
104
+ };
105
+
106
+ const computed = (fn) => {
107
+ const sig = signal(undefined);
108
+ effect(() => {
109
+ sig.value = fn();
110
+ });
111
+ return sig;
112
+ };
113
+
114
+
115
+ // ============= SHADOW DOM SUPPORT =============
116
+ // Marker symbol to identify shadowDOM directives
117
+ const SHADOW_DOM_MARKER = Symbol('lightview.shadowDOM');
118
+ const LIST_MARKER = Symbol('lightview.list');
119
+
120
+ /**
121
+ * Create a shadowDOM directive marker
122
+ * @param {Object} attributes - { mode: 'open'|'closed', styles?: string[], adoptedStyleSheets?: CSSStyleSheet[] }
123
+ * @param {Array} children - Children to render inside the shadow root
124
+ * @returns {Object} - Marker object for setupChildren to process
125
+ */
126
+ const createShadowDOMMarker = (attributes, children) => ({
127
+ [SHADOW_DOM_MARKER]: true,
128
+ mode: attributes.mode || 'open',
129
+ styles: attributes.styles || [],
130
+ adoptedStyleSheets: attributes.adoptedStyleSheets || [],
131
+ children
132
+ });
133
+
134
+ /**
135
+ * Check if an object is a shadowDOM marker
136
+ */
137
+ const isShadowDOMMarker = (obj) => obj && typeof obj === 'object' && obj[SHADOW_DOM_MARKER] === true;
138
+
139
+ /**
140
+ * Process a shadowDOM marker by attaching shadow root and rendering children
141
+ * @param {Object} marker - The shadowDOM marker
142
+ * @param {HTMLElement} parentNode - The DOM node to attach shadow to
143
+ */
144
+ const processShadowDOM = (marker, parentNode) => {
145
+ // Don't attach if already has shadow root
146
+ if (parentNode.shadowRoot) {
147
+ console.warn('Lightview: Element already has a shadowRoot, skipping shadowDOM directive');
148
+ return;
149
+ }
150
+
151
+ // Attach shadow root
152
+ const shadowRoot = parentNode.attachShadow({ mode: marker.mode });
153
+
154
+ // Split adoptedStyleSheets into sheets and urls
155
+ const sheets = [];
156
+ const linkUrls = [...(marker.styles || [])];
157
+
158
+ if (marker.adoptedStyleSheets && marker.adoptedStyleSheets.length > 0) {
159
+ marker.adoptedStyleSheets.forEach(item => {
160
+ if (item instanceof CSSStyleSheet) {
161
+ sheets.push(item);
162
+ } else if (typeof item === 'string') {
163
+ linkUrls.push(item);
164
+ }
165
+ });
166
+ }
167
+
168
+ // Handle adoptedStyleSheets (modern, efficient approach)
169
+ if (sheets.length > 0) {
170
+ try {
171
+ shadowRoot.adoptedStyleSheets = sheets;
172
+ } catch (e) {
173
+ console.warn('Lightview: adoptedStyleSheets not supported');
174
+ }
175
+ }
176
+
177
+ // Inject stylesheet links
178
+ for (const styleUrl of linkUrls) {
179
+ const link = document.createElement('link');
180
+ link.rel = 'stylesheet';
181
+ link.href = styleUrl;
182
+ shadowRoot.appendChild(link);
183
+ }
184
+
185
+ // Setup children inside shadow root
186
+ if (marker.children && marker.children.length > 0) {
187
+ setupChildrenInTarget(marker.children, shadowRoot);
188
+ }
189
+ };
190
+
191
+ // ============= REACTIVE UI =============
192
+ const SVG_TAGS = new Set([
193
+ 'svg', 'path', 'circle', 'rect', 'line', 'polyline', 'polygon', 'g', 'defs', 'marker',
194
+ 'pattern', 'mask', 'image', 'text', 'tspan', 'foreignObject', 'use', 'symbol', 'clipPath',
195
+ 'linearGradient', 'radialGradient', 'stop', 'filter', 'animate', 'animateMotion',
196
+ 'animateTransform', 'mpath', 'desc', 'metadata', 'title', 'feBlend', 'feColorMatrix',
197
+ 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting',
198
+ 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB',
199
+ 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode',
200
+ 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight',
201
+ 'feTile', 'feTurbulence', 'view'
202
+ ]);
203
+
204
+ const domToElement = new WeakMap();
205
+
206
+ const wrapDomElement = (domNode, tag, attributes = {}, children = []) => {
207
+ const el = {
208
+ tag,
209
+ attributes,
210
+ children,
211
+ get domEl() { return domNode; }
212
+ };
213
+ const proxy = makeReactive(el);
214
+ domToElement.set(domNode, proxy);
215
+ return proxy;
216
+ };
217
+
218
+ const element = (tag, attributes = {}, children = []) => {
219
+ if (customTags[tag]) tag = customTags[tag];
220
+ // If tag is a function (component), call it and process the result
221
+ if (typeof tag === 'function') {
222
+ const result = tag({ ...attributes }, children);
223
+ return processComponentResult(result);
224
+ }
225
+
226
+ // Special handling for shadowDOM pseudo-element
227
+ if (tag === 'shadowDOM') {
228
+ return createShadowDOMMarker(attributes, children);
229
+ }
230
+
231
+ const isSvg = SVG_TAGS.has(tag.toLowerCase());
232
+ const domNode = isSvg
233
+ ? document.createElementNS('http://www.w3.org/2000/svg', tag)
234
+ : document.createElement(tag);
235
+ const proxy = wrapDomElement(domNode, tag, attributes, children);
236
+ proxy.attributes = attributes;
237
+ proxy.children = children;
238
+ return proxy;
239
+ };
240
+
241
+ // Process component function return value (HTML string, DOM node, vDOM, or Object DOM)
242
+ const processComponentResult = (result) => {
243
+ if (!result) return null;
244
+
245
+ // Already a Lightview element
246
+ if (result.domEl) return result;
247
+
248
+ // DOM node - wrap it
249
+ if (result instanceof HTMLElement) {
250
+ return wrapDomElement(result, result.tagName.toLowerCase(), {}, []);
251
+ }
252
+
253
+ // HTML string - parse and wrap
254
+ if (typeof result === 'string') {
255
+ const template = document.createElement('template');
256
+ template.innerHTML = result.trim();
257
+ const content = template.content;
258
+ // If single element, return it; otherwise wrap in a fragment-like span
259
+ if (content.childNodes.length === 1 && content.firstChild instanceof HTMLElement) {
260
+ const el = content.firstChild;
261
+ return wrapDomElement(el, el.tagName.toLowerCase(), {}, []);
262
+ } else {
263
+ const wrapper = document.createElement('span');
264
+ wrapper.style.display = 'contents';
265
+ wrapper.appendChild(content);
266
+ return wrapDomElement(wrapper, 'span', {}, []);
267
+ }
268
+ }
269
+
270
+ // vDOM object with tag property
271
+ if (typeof result === 'object' && result.tag) {
272
+ return element(result.tag, result.attributes || {}, result.children || []);
273
+ }
274
+
275
+ // Object DOM syntax will be handled by processChild hook in lightview-x
276
+ // But we can do basic detection here
277
+ if (typeof result === 'object') {
278
+ const keys = Object.keys(result);
279
+ if (keys.length === 1 && typeof result[keys[0]] === 'object') {
280
+ const tag = keys[0];
281
+ const content = result[tag];
282
+ const { children, ...attributes } = content;
283
+ return element(tag, attributes, children || []);
284
+ }
285
+ }
286
+
287
+ return null;
288
+ };
289
+
290
+ const makeReactive = (el) => {
291
+ const domNode = el.domEl;
292
+
293
+ return new Proxy(el, {
294
+ set(target, prop, value) {
295
+ if (prop === 'attributes') {
296
+ target[prop] = makeReactiveAttributes(value, domNode);
297
+ } else if (prop === 'children') {
298
+ target[prop] = setupChildren(value, domNode);
299
+ } else {
300
+ target[prop] = value;
301
+ }
302
+ return true;
303
+ }
304
+ });
305
+ };
306
+
307
+ // Boolean attributes that should be present/absent rather than having a value
308
+ const BOOLEAN_ATTRIBUTES = new Set([
309
+ 'disabled', 'checked', 'readonly', 'required', 'hidden', 'autofocus',
310
+ 'autoplay', 'controls', 'loop', 'muted', 'default', 'defer', 'async',
311
+ 'novalidate', 'formnovalidate', 'open', 'selected', 'multiple', 'reversed',
312
+ 'ismap', 'nomodule', 'playsinline', 'allowfullscreen', 'inert'
313
+ ]);
314
+
315
+ // Set attribute with proper handling of boolean attributes and undefined/null values
316
+ const setAttributeValue = (domNode, key, value) => {
317
+ const isBooleanAttr = BOOLEAN_ATTRIBUTES.has(key.toLowerCase());
318
+
319
+ if (value === null || value === undefined) {
320
+ domNode.removeAttribute(key);
321
+ } else if (isBooleanAttr) {
322
+ if (value && value !== 'false') {
323
+ domNode.setAttribute(key, '');
324
+ } else {
325
+ domNode.removeAttribute(key);
326
+ }
327
+ } else {
328
+ domNode.setAttribute(key, value);
329
+ }
330
+ };
331
+
332
+ const makeReactiveAttributes = (attributes, domNode) => {
333
+ const reactiveAttrs = {};
334
+
335
+ for (let [key, value] of Object.entries(attributes)) {
336
+ if (key === 'onmount' || key === 'onunmount') {
337
+ const state = getOrSet(nodeState, domNode, nodeStateFactory);
338
+ state[key] = value;
339
+
340
+ if (key === 'onmount' && domNode.isConnected) {
341
+ value(domNode);
342
+ }
343
+ } else if (key.startsWith('on')) {
344
+ // Event handler
345
+ if (typeof value === 'function') {
346
+ // Function handler - use addEventListener
347
+ const eventName = key.slice(2).toLowerCase();
348
+ domNode.addEventListener(eventName, value);
349
+ } else if (typeof value === 'string') {
350
+ // String handler (from parsed HTML) - use setAttribute
351
+ // Browser will compile the string into a handler function
352
+ domNode.setAttribute(key, value);
353
+ }
354
+ reactiveAttrs[key] = value;
355
+ } else if (typeof value === 'function') {
356
+ // Reactive binding
357
+ const runner = effect(() => {
358
+ const result = value();
359
+ if (key === 'style' && typeof result === 'object') {
360
+ Object.assign(domNode.style, result);
361
+ } else {
362
+ setAttributeValue(domNode, key, result);
363
+ }
364
+ });
365
+ trackEffect(domNode, runner);
366
+ reactiveAttrs[key] = value;
367
+ } else if (key === 'style' && typeof value === 'object') {
368
+ // Handle style object which may contain reactive values
369
+ Object.entries(value).forEach(([styleKey, styleValue]) => {
370
+ if (typeof styleValue === 'function') {
371
+ const runner = effect(() => {
372
+ domNode.style[styleKey] = styleValue();
373
+ });
374
+ trackEffect(domNode, runner);
375
+ } else {
376
+ domNode.style[styleKey] = styleValue;
377
+ }
378
+ });
379
+ reactiveAttrs[key] = value;
380
+ } else {
381
+ // Static attribute - handle undefined/null/boolean properly
382
+ setAttributeValue(domNode, key, value);
383
+ reactiveAttrs[key] = value;
384
+ }
385
+ }
386
+
387
+ return reactiveAttrs;
388
+ };
389
+
390
+ /**
391
+ * Core child processing logic - shared between setupChildren and setupChildrenInTarget
392
+ * @param {Array} children - Children to process
393
+ * @param {HTMLElement|ShadowRoot} targetNode - Where to append children
394
+ * @param {boolean} clearExisting - Whether to clear existing content
395
+ * @returns {Array} - Processed child elements
396
+ */
397
+ const processChildren = (children, targetNode, clearExisting = true) => {
398
+ if (clearExisting && targetNode.innerHTML !== undefined) {
399
+ targetNode.innerHTML = ''; // Clear existing
400
+ }
401
+ const childElements = [];
402
+
403
+ // Check if we're processing children of script or style elements
404
+ // These need raw text content preserved, not reactive transformations
405
+ const isSpecialElement = targetNode.tagName &&
406
+ (targetNode.tagName.toLowerCase() === 'script' || targetNode.tagName.toLowerCase() === 'style');
407
+
408
+ for (let child of children) {
409
+ // Allow extensions to transform children (e.g., template literals)
410
+ // BUT skip for script/style elements which need raw content
411
+ if (Lightview.hooks.processChild && !isSpecialElement) {
412
+ child = Lightview.hooks.processChild(child) ?? child;
413
+ }
414
+
415
+ // Handle nested arrays (flattening)
416
+ if (Array.isArray(child)) {
417
+ childElements.push(...processChildren(child, targetNode, false));
418
+ continue;
419
+ }
420
+
421
+ // Handle shadowDOM markers - attach shadow to parent and process shadow children
422
+ if (isShadowDOMMarker(child)) {
423
+ // targetNode is the parent element that should get the shadow root
424
+ // For ShadowRoot targets, we can't attach another shadow, so warn
425
+ if (targetNode instanceof ShadowRoot) {
426
+ console.warn('Lightview: Cannot nest shadowDOM inside another shadowDOM');
427
+ continue;
428
+ }
429
+ processShadowDOM(child, targetNode);
430
+ continue;
431
+ }
432
+
433
+ const type = typeof child;
434
+ if (type === 'function') {
435
+ const result = child();
436
+ // Determine if the result implies complex content (DOM/vDOM/Array)
437
+ // Treat as complex if it's an object (including arrays) but not null
438
+ const isComplex = result && (typeof result === 'object' || Array.isArray(result));
439
+
440
+ if (isComplex) {
441
+ // Reactive element, vDOM object, or list of items
442
+ // Use a stable wrapper div to hold the reactive content
443
+ const wrapper = document.createElement('span');
444
+ wrapper.style.display = 'contents';
445
+ targetNode.appendChild(wrapper);
446
+
447
+ let runner;
448
+ let oldState = []; // State for list reconciliation
449
+
450
+ const update = () => {
451
+ const val = child();
452
+ // Check if wrapper is still in the DOM (skip check on first run)
453
+ if (runner && !wrapper.parentNode) {
454
+ runner.stop();
455
+ return;
456
+ }
457
+
458
+ if (val && val[LIST_MARKER]) {
459
+ // Optimized list reconciliation
460
+ oldState = reconcileList(wrapper, oldState, val.items, val.fn);
461
+ } else {
462
+ // Full re-render fallback
463
+ oldState = []; // Reset optimization state
464
+ const childrenToProcess = Array.isArray(val) ? val : [val];
465
+ // processChildren handles clearing existing content via 3rd arg=true
466
+ processChildren(childrenToProcess, wrapper, true);
467
+ }
468
+ };
469
+
470
+ runner = effect(update);
471
+ trackEffect(wrapper, runner);
472
+ childElements.push(child);
473
+ } else {
474
+ // Reactive text node for primitives
475
+ const textNode = document.createTextNode('');
476
+ targetNode.appendChild(textNode);
477
+ const runner = effect(() => {
478
+ const val = child();
479
+ textNode.textContent = val !== undefined ? val : '';
480
+ });
481
+ trackEffect(textNode, runner);
482
+ childElements.push(child);
483
+ }
484
+ } else if (['string', 'number', 'boolean', 'symbol'].includes(type)) {
485
+ // Static text
486
+ targetNode.appendChild(document.createTextNode(child));
487
+ childElements.push(child);
488
+ } else if (child && type === 'object' && child.tag) {
489
+ // Child element (already wrapped or plain object) - tag can be string or function
490
+ const childEl = child.domEl ? child : element(child.tag, child.attributes || {}, child.children || []);
491
+ targetNode.appendChild(childEl.domEl);
492
+ childElements.push(childEl);
493
+ }
494
+ }
495
+
496
+ return childElements;
497
+ };
498
+
499
+ /**
500
+ * Efficiently reconcile list items to minimize DOM operations
501
+ */
502
+ const reconcileList = (parent, oldState, newItems, renderFn) => {
503
+ const reuse = new Map();
504
+
505
+ // 1. Map old nodes for potential reuse
506
+ for (const entry of oldState) {
507
+ let list = reuse.get(entry.item);
508
+ if (!list) { list = []; reuse.set(entry.item, list); }
509
+ list.push(entry.node);
510
+ }
511
+
512
+ const newState = [];
513
+
514
+ // 2. Build new state, reusing nodes where possible
515
+ for (const item of newItems) {
516
+ let node;
517
+ const list = reuse.get(item);
518
+ if (list && list.length > 0) {
519
+ // Reuse existing node for this item
520
+ node = list.shift();
521
+ } else {
522
+ // Create new node from render function
523
+ const raw = renderFn(item);
524
+
525
+ // Use a temporary container to process the result into DOM nodes
526
+ const temp = document.createElement('div');
527
+ // processChildren returns the Lightview wrappers/proxies; we need the DOM nodes
528
+ processChildren([raw], temp, false);
529
+
530
+ if (temp.childNodes.length === 1) {
531
+ node = temp.firstChild;
532
+ } else if (temp.childNodes.length === 0) {
533
+ node = document.createTextNode('');
534
+ } else {
535
+ // Multiple nodes returned for one item, wrap in span
536
+ node = document.createElement('span');
537
+ node.style.display = 'contents';
538
+ while (temp.firstChild) {
539
+ node.appendChild(temp.firstChild);
540
+ }
541
+ }
542
+ }
543
+ newState.push({ item, node });
544
+ }
545
+
546
+ // 3. Remove unused nodes
547
+ for (const list of reuse.values()) {
548
+ for (const n of list) {
549
+ n.remove();
550
+ }
551
+ }
552
+
553
+ // 4. Reorder/Append nodes in valid order
554
+ let sibling = parent.firstChild;
555
+ for (const entry of newState) {
556
+ const domNode = entry.node;
557
+ if (sibling === domNode) {
558
+ // Already in place
559
+ sibling = sibling.nextSibling;
560
+ } else {
561
+ // Needs to be moved or inserted
562
+ parent.insertBefore(domNode, sibling);
563
+ }
564
+ }
565
+
566
+ return newState;
567
+ };
568
+
569
+ /**
570
+ * Setup children in a target node (for shadow roots and other targets)
571
+ * Does not clear existing content
572
+ */
573
+ const setupChildrenInTarget = (children, targetNode) => {
574
+ return processChildren(children, targetNode, false);
575
+ };
576
+
577
+ /**
578
+ * Setup children on a DOM node, clearing existing content
579
+ */
580
+ const setupChildren = (children, domNode) => {
581
+ return processChildren(children, domNode, true);
582
+ };
583
+
584
+ // ============= EXPORTS =============
585
+ const enhance = (selectorOrNode, options = {}) => {
586
+ const domNode = typeof selectorOrNode === 'string'
587
+ ? document.querySelector(selectorOrNode)
588
+ : selectorOrNode;
589
+
590
+ // If it's already a Lightview element, use its domEl
591
+ const node = domNode.domEl || domNode;
592
+ if (!(node instanceof HTMLElement)) return null;
593
+
594
+ const tagName = node.tagName.toLowerCase();
595
+ let el = domToElement.get(node);
596
+
597
+ if (!el) {
598
+ el = wrapDomElement(node, tagName);
599
+ }
600
+
601
+ const { innerText, innerHTML, ...attrs } = options;
602
+
603
+ if (innerText !== undefined) {
604
+ if (typeof innerText === 'function') {
605
+ effect(() => { node.innerText = innerText(); });
606
+ } else {
607
+ node.innerText = innerText;
608
+ }
609
+ }
610
+
611
+ if (innerHTML !== undefined) {
612
+ if (typeof innerHTML === 'function') {
613
+ effect(() => { node.innerHTML = innerHTML(); });
614
+ } else {
615
+ node.innerHTML = innerHTML;
616
+ }
617
+ }
618
+
619
+ if (Object.keys(attrs).length > 0) {
620
+ // Merge with existing attributes or simply set them triggers the proxy
621
+ el.attributes = attrs;
622
+ }
623
+
624
+ return el;
625
+ };
626
+
627
+ const $ = (cssSelectorOrElement, startingDomEl = document.body) => {
628
+ const el = typeof cssSelectorOrElement === 'string' ? startingDomEl.querySelector(cssSelectorOrElement) : cssSelectorOrElement;
629
+ if (!el) return null;
630
+ Object.defineProperty(el, 'content', {
631
+ value(child, location = 'inner') {
632
+ location = location.toLowerCase();
633
+ const tags = Lightview.tags;
634
+
635
+ // Check if target element is script or style
636
+ const isSpecialElement = el.tagName &&
637
+ (el.tagName.toLowerCase() === 'script' || el.tagName.toLowerCase() === 'style');
638
+
639
+ const array = (Array.isArray(child) ? child : [child]).map(item => {
640
+ // Allow extensions to transform children (e.g., Object DOM syntax)
641
+ // BUT skip for script/style elements which need raw content
642
+ if (Lightview.hooks.processChild && !isSpecialElement) {
643
+ item = Lightview.hooks.processChild(item) ?? item;
644
+ }
645
+ if (item.tag && !item.domEl) {
646
+ return element(item.tag, item.attributes || {}, item.children || []).domEl;
647
+ } else {
648
+ return item.domEl || item;
649
+ }
650
+ });
651
+
652
+ if (location === 'shadow') {
653
+ let shadow = el.shadowRoot;
654
+ if (!shadow) {
655
+ shadow = el.attachShadow({ mode: 'open' });
656
+ }
657
+ shadow.innerHTML = '';
658
+ array.forEach(item => {
659
+ shadow.appendChild(item);
660
+ });
661
+ return el;
662
+ }
663
+
664
+ if (location === 'inner') {
665
+ el.innerHTML = '';
666
+ array.forEach(item => {
667
+ el.appendChild(item);
668
+ });
669
+ return el;
670
+ }
671
+
672
+ if (location === 'outer') {
673
+ el.replaceWith(...array);
674
+ return el;
675
+ }
676
+
677
+ if (location === 'afterbegin' || location === 'afterend') {
678
+ array.reverse();
679
+ }
680
+ array.forEach(item => {
681
+ el.insertAdjacentElement(location, item);
682
+ });
683
+ return el;
684
+ },
685
+ configurable: true,
686
+ writable: true
687
+ });
688
+ return el;
689
+ };
690
+
691
+ const customTags = {}
692
+ const tags = new Proxy({}, {
693
+ get(_, tag) {
694
+ if (tag === "_customTags") return { ...customTags };
695
+ const wrapper = (...args) => {
696
+ let attributes = {};
697
+ let children = args;
698
+ const arg0 = args[0];
699
+ if (args.length > 0 && arg0 && typeof arg0 === 'object' && !arg0.tag && !arg0.domEl && !Array.isArray(arg0)) {
700
+ attributes = arg0;
701
+ children = args.slice(1);
702
+ }
703
+ return element(customTags[tag] || tag, attributes, children.flat());
704
+ };
705
+
706
+ const original = customTags[tag];
707
+ if (original) {
708
+ Object.assign(wrapper, original);
709
+ }
710
+
711
+ return wrapper;
712
+ },
713
+ set(_, tag, value) {
714
+ customTags[tag] = value;
715
+ return true;
716
+ }
717
+ });
718
+
719
+ const Lightview = {
720
+ signal,
721
+ computed,
722
+ effect,
723
+ element, // do not document this
724
+ enhance,
725
+ tags,
726
+ $,
727
+ // Extension hooks
728
+ hooks: {
729
+ onNonStandardHref: null,
730
+ processChild: null
731
+ },
732
+ // Internals exposed for extensions
733
+ internals: {
734
+ domToElement,
735
+ wrapDomElement,
736
+ setupChildren
737
+ }
738
+ };
739
+
740
+ // Export for use
741
+ if (typeof module !== 'undefined' && module.exports) {
742
+ module.exports = Lightview;
743
+ }
744
+ if (typeof window !== 'undefined') {
745
+ window.Lightview = Lightview;
746
+
747
+ // Global click handler delegates to hook if registered
748
+ window.addEventListener('click', (e) => {
749
+ if (Lightview.hooks.onNonStandardHref) {
750
+ Lightview.hooks.onNonStandardHref(e);
751
+ }
752
+ });
753
+
754
+ // Automatic Cleanup & Lifecycle Hooks
755
+ const walkNodes = (node, fn) => { fn(node); node.childNodes?.forEach(n => walkNodes(n, fn)); };
756
+
757
+ const cleanupNode = (node) => walkNodes(node, n => {
758
+ const s = nodeState.get(n);
759
+ if (s) {
760
+ s.effects?.forEach(e => e.stop());
761
+ s.onunmount?.(n);
762
+ nodeState.delete(n);
763
+ }
764
+ });
765
+
766
+ const mountNode = (node) => walkNodes(node, n => {
767
+ nodeState.get(n)?.onmount?.(n);
768
+ });
769
+
770
+ const observer = new MutationObserver((mutations) => {
771
+ mutations.forEach((mutation) => {
772
+ mutation.removedNodes.forEach(cleanupNode);
773
+ mutation.addedNodes.forEach(mountNode);
774
+ });
775
+ });
776
+
777
+ // Wait for DOM to be ready before observing
778
+ const startObserving = () => {
779
+ if (document.body) {
780
+ observer.observe(document.body, {
781
+ childList: true,
782
+ subtree: true
783
+ });
784
+ }
785
+ };
786
+
787
+ if (document.readyState === 'loading') {
788
+ document.addEventListener('DOMContentLoaded', startObserving);
789
+ } else {
790
+ startObserving();
791
+ }
792
+ }
793
+ })();