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/lightview-x.js ADDED
@@ -0,0 +1,1577 @@
1
+ (() => {
2
+ // ============= LIGHTVIEW-X =============
3
+ // Hypermedia extension for Lightview
4
+ // Adds: src attribute fetching, href navigation, DOM-to-element conversion, template literals, named registries, Object DOM syntax
5
+
6
+ const STANDARD_SRC_TAGS = ['img', 'script', 'iframe', 'video', 'audio', 'source', 'track', 'embed', 'input'];
7
+ const isStandardSrcTag = (tagName) => STANDARD_SRC_TAGS.includes(tagName) || tagName.startsWith('lv-');
8
+ const STANDARD_HREF_TAGS = ['a', 'area', 'base', 'link'];
9
+
10
+ /**
11
+ * Check if a string is a valid HTML tag name
12
+ * @param {string} name - The tag name to check
13
+ * @returns {boolean}
14
+ */
15
+ const isValidTagName = (name) => {
16
+ if (typeof name !== 'string' || name.length === 0 || name === 'children') {
17
+ return false;
18
+ }
19
+ // Non-strict mode: accept anything that looks reasonable
20
+ return true;
21
+ };
22
+
23
+ /**
24
+ * Check if an object is in Object DOM syntax
25
+ * Object DOM: { div: { class: "foo", children: [...] } }
26
+ * vDOM: { tag: "div", attributes: {...}, children: [...] }
27
+ * @param {any} obj
28
+ * @returns {boolean}
29
+ */
30
+ const isObjectDOM = (obj) => {
31
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) return false;
32
+ if (obj.tag || obj.domEl) return false; // Already vDOM or live element
33
+
34
+ const keys = Object.keys(obj);
35
+ if (keys.length === 0) return false;
36
+
37
+ // Object DOM has exactly one key (the tag name or component name) whose value is an object
38
+ // That object may contain attributes and optionally a 'children' property
39
+ if (keys.length === 1) {
40
+ const tag = keys[0];
41
+ const value = obj[tag];
42
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;
43
+
44
+ // Otherwise check if it's a valid tag name
45
+ return isValidTagName(tag);
46
+ }
47
+
48
+ return false;
49
+ };
50
+
51
+ /**
52
+ * Convert Object DOM syntax to vDOM syntax (recursive)
53
+ * @param {any} obj - Object in Object DOM format or any child
54
+ * @returns {any} - Converted to vDOM format
55
+ */
56
+ const convertObjectDOM = (obj) => {
57
+ // Not an object or array - return as-is (strings, numbers, functions, etc.)
58
+ if (typeof obj !== 'object' || obj === null) return obj;
59
+
60
+ // Array - recursively convert children
61
+ if (Array.isArray(obj)) {
62
+ return obj.map(convertObjectDOM);
63
+ }
64
+
65
+ // Already vDOM format - recurse into children only
66
+ if (obj.tag) {
67
+ return {
68
+ ...obj,
69
+ children: obj.children ? convertObjectDOM(obj.children) : []
70
+ };
71
+ }
72
+
73
+ // Live element - pass through
74
+ if (obj.domEl) return obj;
75
+
76
+ // Check for Object DOM syntax
77
+ if (isObjectDOM(obj)) {
78
+ const tagKey = Object.keys(obj)[0];
79
+ const content = obj[tagKey];
80
+
81
+ // Access custom registry via Lightview.tags._customTags if available
82
+ let tag = tagKey;
83
+ if (typeof window !== 'undefined' && window.Lightview && window.Lightview.tags) {
84
+ const customTags = window.Lightview.tags._customTags || {};
85
+ if (customTags[tagKey]) {
86
+ tag = customTags[tagKey];
87
+ }
88
+ }
89
+
90
+ // Extract children and attributes
91
+ const { children, ...attributes } = content;
92
+
93
+ return {
94
+ tag,
95
+ attributes,
96
+ children: children ? convertObjectDOM(children) : []
97
+ };
98
+ }
99
+
100
+ // Unknown object format - return as-is
101
+ return obj;
102
+ };
103
+
104
+ // ============= COMPONENT CONFIGURATION =============
105
+ // Global configuration for Lightview components
106
+
107
+ const DAISYUI_CDN = 'https://cdn.jsdelivr.net/npm/daisyui@3.9.4/dist/full.min.css';
108
+
109
+ // Component configuration (set by initComponents)
110
+ const componentConfig = {
111
+ initialized: false,
112
+ shadowDefault: true, // Default: components use shadow DOM
113
+ daisyStyleSheet: null,
114
+ themeStyleSheet: null, // Global theme stylesheet
115
+ componentStyleSheets: new Map(),
116
+ customStyleSheets: new Map() // Registry for named custom stylesheets
117
+ };
118
+
119
+ /**
120
+ * Register a named stylesheet for use in components
121
+ * @param {string} nameOrIdOrUrl - The name/ID/URL of the stylesheet
122
+ * @param {string} [cssText] - Optional raw CSS content. If provided, nameOrIdOrUrl is treated as a name.
123
+ * @returns {Promise<void>}
124
+ */
125
+ const registerStyleSheet = async (nameOrIdOrUrl, cssText) => {
126
+ if (componentConfig.customStyleSheets.has(nameOrIdOrUrl)) return;
127
+
128
+ try {
129
+ let finalCss = cssText;
130
+
131
+ if (finalCss === undefined) {
132
+ if (nameOrIdOrUrl.startsWith('#')) {
133
+ // ID selector - search synchronously
134
+ const el = document.querySelector(nameOrIdOrUrl);
135
+ if (el) {
136
+ finalCss = el.textContent;
137
+ } else {
138
+ throw new Error(`Style block '${nameOrIdOrUrl}' not found`);
139
+ }
140
+ } else {
141
+ // Assume URL
142
+ const response = await fetch(nameOrIdOrUrl);
143
+ if (!response.ok) throw new Error(`Fetch failed: ${response.status}`);
144
+ finalCss = await response.text();
145
+ }
146
+ }
147
+
148
+ if (finalCss !== undefined) {
149
+ const sheet = new CSSStyleSheet();
150
+ sheet.replaceSync(finalCss);
151
+ componentConfig.customStyleSheets.set(nameOrIdOrUrl, sheet);
152
+ }
153
+ } catch (e) {
154
+ console.error(`LightviewX: Failed to register stylesheet '${nameOrIdOrUrl}':`, e);
155
+ }
156
+ };
157
+
158
+ // Theme Signal
159
+ // Helper to safely get local storage
160
+ const getSavedTheme = () => {
161
+ try {
162
+ if (typeof localStorage !== 'undefined') {
163
+ return localStorage.getItem('lightview-theme');
164
+ }
165
+ } catch (e) {
166
+ return null;
167
+ }
168
+ };
169
+
170
+ // Theme Signal
171
+ const themeSignal = typeof window !== 'undefined' && window.Lightview ? window.Lightview.signal(
172
+ (typeof document !== 'undefined' && document.documentElement.getAttribute('data-theme')) ||
173
+ getSavedTheme() ||
174
+ 'light'
175
+ ) : { value: 'light' };
176
+
177
+ /**
178
+ * Set the global theme for Lightview components (updates signal only)
179
+ * @param {string} themeName - The name of the theme (e.g., 'light', 'dark', 'cyberpunk')
180
+ */
181
+ const setTheme = (themeName) => {
182
+ if (!themeName) return;
183
+ // Determine base theme (light or dark) for the main document
184
+ // Determine base theme (light or dark) for the main document
185
+ // const darkThemes = ['dark', 'aqua', 'black', 'business', 'coffee', 'dim', 'dracula', 'forest', 'halloween', 'luxury', 'night', 'sunset', 'synthwave'];
186
+ // const baseTheme = darkThemes.includes(themeName) ? 'dark' : 'light';
187
+ document.documentElement.setAttribute('data-theme', themeName);
188
+
189
+ // Update signal
190
+ if (themeSignal && themeSignal.value !== themeName) {
191
+ themeSignal.value = themeName;
192
+ }
193
+
194
+ // Persist preference
195
+ try {
196
+ localStorage.setItem('lightview-theme', themeName);
197
+ } catch (e) {
198
+ // Ignore storage errors
199
+ }
200
+ };
201
+
202
+ /**
203
+ * Register a global theme stylesheet for all components
204
+ * @param {string} url - URL to the CSS file
205
+ * @returns {Promise<void>}
206
+ */
207
+ const registerThemeSheet = async (url) => {
208
+ try {
209
+ const response = await fetch(url);
210
+ if (!response.ok) throw new Error(`Failed to fetch theme CSS: ${response.status}`);
211
+ const cssText = await response.text();
212
+ const sheet = new CSSStyleSheet();
213
+ sheet.replaceSync(cssText);
214
+ componentConfig.themeStyleSheet = sheet;
215
+ } catch (e) {
216
+ console.error(`LightviewX: Failed to register theme stylesheet '${url}':`, e);
217
+ }
218
+ };
219
+
220
+ /**
221
+ * Initialize Lightview components
222
+ * Preloads DaisyUI stylesheet for shadow DOM usage
223
+ * @param {Object} options
224
+ * @param {boolean} options.shadowDefault - Whether components use shadow DOM by default (default: true)
225
+ * @returns {Promise<void>}
226
+ */
227
+ const initComponents = async (options = {}) => {
228
+ const { shadowDefault = true } = options;
229
+
230
+ componentConfig.shadowDefault = shadowDefault;
231
+
232
+ if (shadowDefault) {
233
+ // Preload DaisyUI stylesheet for adopted stylesheets
234
+ try {
235
+ const response = await fetch(DAISYUI_CDN);
236
+ if (!response.ok) {
237
+ throw new Error(`Failed to fetch DaisyUI CSS: ${response.status}`);
238
+ }
239
+ const cssText = await response.text();
240
+ const sheet = new CSSStyleSheet();
241
+ sheet.replaceSync(cssText);
242
+ componentConfig.daisyStyleSheet = sheet;
243
+ } catch (e) {
244
+ console.error('LightviewX: Failed to preload DaisyUI stylesheet:', e);
245
+ // Continue without DaisyUI - components will still work, just without DaisyUI styles in shadow
246
+ }
247
+ }
248
+
249
+ componentConfig.initialized = true;
250
+ };
251
+ (async () => await initComponents())();
252
+
253
+ /**
254
+ * Get or create a CSSStyleSheet for a component's CSS file
255
+ * @param {string} cssUrl - URL to the component's CSS file
256
+ * @returns {Promise<CSSStyleSheet|null>}
257
+ */
258
+ const getComponentStyleSheet = async (cssUrl) => {
259
+ // Return cached sheet if available
260
+ if (componentConfig.componentStyleSheets.has(cssUrl)) {
261
+ return componentConfig.componentStyleSheets.get(cssUrl);
262
+ }
263
+
264
+ try {
265
+ const response = await fetch(cssUrl);
266
+ if (!response.ok) {
267
+ throw new Error(`Failed to fetch component CSS: ${response.status}`);
268
+ }
269
+ const cssText = await response.text();
270
+
271
+ const sheet = new CSSStyleSheet();
272
+ sheet.replaceSync(cssText);
273
+ componentConfig.componentStyleSheets.set(cssUrl, sheet);
274
+ return sheet;
275
+ } catch (e) {
276
+ console.error(`LightviewX: Failed to create stylesheet for ${cssUrl}:`, e);
277
+ return null;
278
+ }
279
+ };
280
+
281
+ /**
282
+ * Synchronously get cached component stylesheet (returns null if not yet loaded)
283
+ * @param {string} cssUrl
284
+ * @returns {CSSStyleSheet|null}
285
+ */
286
+ const getComponentStyleSheetSync = (cssUrl) => componentConfig.componentStyleSheets.get(cssUrl) || null;
287
+
288
+ /**
289
+ * Check if a component should use shadow DOM based on props and global default
290
+ * @param {boolean|undefined} useShadowProp - The useShadow prop passed to the component
291
+ * @returns {boolean}
292
+ */
293
+ const shouldUseShadow = (useShadowProp) => {
294
+ // Explicit prop value takes precedence
295
+ if (useShadowProp !== undefined) {
296
+ return useShadowProp;
297
+ }
298
+ // Fall back to global default
299
+ return componentConfig.shadowDefault;
300
+ };
301
+
302
+ /**
303
+ * Get the adopted stylesheets for a component
304
+ * @param {string} componentCssUrl - URL to the component's CSS file
305
+ * @param {string[]} requestedSheets - Array of stylesheet URLs to include
306
+ * @returns {(CSSStyleSheet|string)[]} - Mixed array of StyleSheet objects and URL strings (for link fallbacks)
307
+ */
308
+ const getAdoptedStyleSheets = (componentCssUrl, requestedSheets = []) => {
309
+ const result = [];
310
+
311
+ // Add global DaisyUI sheet
312
+ if (componentConfig.daisyStyleSheet) {
313
+ result.push(componentConfig.daisyStyleSheet);
314
+ } else {
315
+ result.push(DAISYUI_CDN);
316
+ }
317
+
318
+ // Add global Theme sheet (overrides default Daisy variables)
319
+ if (componentConfig.themeStyleSheet) {
320
+ result.push(componentConfig.themeStyleSheet);
321
+ }
322
+
323
+ // Add component-specific sheet
324
+ if (componentCssUrl) {
325
+ const componentSheet = componentConfig.componentStyleSheets.get(componentCssUrl);
326
+ if (componentSheet) {
327
+ result.push(componentSheet);
328
+ }
329
+ }
330
+
331
+ // Process requested sheets
332
+ if (Array.isArray(requestedSheets)) {
333
+ requestedSheets.forEach(url => {
334
+ const sheet = componentConfig.customStyleSheets.get(url);
335
+ if (sheet) {
336
+ // Registered and loaded -> use object
337
+ result.push(sheet);
338
+ } else {
339
+ // Not found -> trigger load, but return string URL for immediate link tag
340
+ registerStyleSheet(url); // Fire and forget
341
+ result.push(url);
342
+ }
343
+ });
344
+ }
345
+
346
+ return result;
347
+ };
348
+
349
+ /**
350
+ * Preload a component's CSS for shadow DOM usage
351
+ * Called by components during their initialization
352
+ * @param {string} cssUrl - URL to the component's CSS file
353
+ * @returns {Promise<void>}
354
+ */
355
+ const preloadComponentCSS = async (cssUrl) => {
356
+ if (!componentConfig.componentStyleSheets.has(cssUrl)) {
357
+ await getComponentStyleSheet(cssUrl);
358
+ }
359
+ };
360
+
361
+ // Named registries for state (used by template literals)
362
+ const stateRegistry = new Map();
363
+
364
+ // ============= STATE (Deep Reactivity) =============
365
+ // Build method lists dynamically from prototypes
366
+ const protoMethods = (proto, test) => Object.getOwnPropertyNames(proto).filter(k => typeof proto[k] === 'function' && test(k));
367
+ const DATE_TRACKING = protoMethods(Date.prototype, k => /^(to|get|valueOf)/.test(k));
368
+ const DATE_MUTATING = protoMethods(Date.prototype, k => /^set/.test(k));
369
+ const ARRAY_TRACKING = ['map', 'forEach', 'filter', 'find', 'findIndex', 'some', 'every', 'reduce',
370
+ 'reduceRight', 'includes', 'indexOf', 'lastIndexOf', 'join', 'slice', 'concat', 'flat', 'flatMap',
371
+ 'at', 'entries', 'keys', 'values'];
372
+ const ARRAY_MUTATING = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse', 'fill', 'copyWithin'];
373
+ const ARRAY_ITERATION = ['map', 'forEach', 'filter', 'find', 'findIndex', 'some', 'every', 'flatMap'];
374
+
375
+ const stateCache = new WeakMap();
376
+ const stateSignals = new WeakMap();
377
+
378
+ // Helper to get or create a value in a map
379
+ const getOrSet = (map, key, factory) => {
380
+ let v = map.get(key);
381
+ if (!v) {
382
+ v = factory();
383
+ map.set(key, v);
384
+ }
385
+ return v;
386
+ };
387
+
388
+ // Shared proxy handler helpers (uses Lightview.signal internally)
389
+ const proxyGet = (target, prop, receiver, signals) => {
390
+ const LV = window.Lightview;
391
+ if (!signals.has(prop)) {
392
+ signals.set(prop, LV.signal(Reflect.get(target, prop, receiver)));
393
+ }
394
+ const val = signals.get(prop).value;
395
+ return typeof val === 'object' && val !== null ? state(val) : val;
396
+ };
397
+
398
+ const proxySet = (target, prop, value, receiver, signals) => {
399
+ const LV = window.Lightview;
400
+ if (!signals.has(prop)) {
401
+ signals.set(prop, LV.signal(Reflect.get(target, prop, receiver)));
402
+ }
403
+ const success = Reflect.set(target, prop, value, receiver);
404
+ if (success) signals.get(prop).value = value;
405
+ return success;
406
+ };
407
+
408
+ const createSpecialProxy = (obj, monitor, trackingProps = []) => {
409
+ const LV = window.Lightview;
410
+ // Get or create the signals map for this object
411
+ const signals = getOrSet(stateSignals, obj, () => new Map());
412
+
413
+ // Create a signal for the monitored property if it doesn't exist
414
+ if (!signals.has(monitor)) {
415
+ const initialValue = typeof obj[monitor] === 'function'
416
+ ? obj[monitor].call(obj)
417
+ : obj[monitor];
418
+ signals.set(monitor, LV.signal(initialValue));
419
+ }
420
+
421
+ // Determine which methods should establish tracking (read the monitor signal)
422
+ const isDate = obj instanceof Date;
423
+ const isArray = Array.isArray(obj);
424
+
425
+ const trackingMethods = isDate ? DATE_TRACKING : isArray ? ARRAY_TRACKING : trackingProps;
426
+ const mutatingMethods = isDate ? DATE_MUTATING : isArray ? ARRAY_MUTATING : [];
427
+
428
+ return new Proxy(obj, {
429
+ get(target, prop, receiver) {
430
+ const value = target[prop];
431
+
432
+ // If accessing a method, wrap it appropriately
433
+ if (typeof value === 'function') {
434
+ const isTracking = trackingMethods.includes(prop);
435
+ const isMutating = mutatingMethods.includes(prop);
436
+
437
+ return function (...args) {
438
+ // For tracking methods, read the signal to establish dependency
439
+ if (isTracking) {
440
+ const sig = signals.get(monitor);
441
+ if (sig) void sig.value;
442
+ }
443
+
444
+ // Get the value before the method call
445
+ const startValue = typeof target[monitor] === 'function'
446
+ ? target[monitor].call(target)
447
+ : target[monitor];
448
+
449
+ // For array iteration methods, wrap the callback to pass state-wrapped elements
450
+ if (isArray && ARRAY_ITERATION.includes(prop) && typeof args[0] === 'function') {
451
+ const originalCallback = args[0];
452
+ args[0] = function (element, index, array) {
453
+ const wrappedElement = typeof element === 'object' && element !== null
454
+ ? state(element)
455
+ : element;
456
+ return originalCallback.call(this, wrappedElement, index, array);
457
+ };
458
+ }
459
+
460
+ // Call the original method
461
+ const result = value.apply(target, args);
462
+
463
+ // Get the value after the method call
464
+ const endValue = typeof target[monitor] === 'function'
465
+ ? target[monitor].call(target)
466
+ : target[monitor];
467
+
468
+ // If the monitored value changed, trigger reactivity
469
+ if (startValue !== endValue || isMutating) {
470
+ const sig = signals.get(monitor);
471
+ if (sig && sig.value !== endValue) {
472
+ sig.value = endValue;
473
+ }
474
+ }
475
+
476
+ return result;
477
+ };
478
+ }
479
+
480
+ // If accessing the monitored property, track it via signal
481
+ if (prop === monitor) {
482
+ const sig = signals.get(monitor);
483
+ return sig ? sig.value : Reflect.get(target, prop, receiver);
484
+ }
485
+
486
+ // For arrays, handle numeric indices for deep reactivity
487
+ if (isArray && !isNaN(parseInt(prop))) {
488
+ const monitorSig = signals.get(monitor);
489
+ if (monitorSig) void monitorSig.value;
490
+ }
491
+
492
+ // Deep reactivity for other properties
493
+ return proxyGet(target, prop, receiver, signals);
494
+ },
495
+ set(target, prop, value, receiver) {
496
+ // If setting the monitored property directly, trigger reactivity
497
+ if (prop === monitor) {
498
+ const success = Reflect.set(target, prop, value, receiver);
499
+ if (success) {
500
+ const sig = signals.get(monitor);
501
+ if (sig) sig.value = value;
502
+ }
503
+ return success;
504
+ }
505
+
506
+ return proxySet(target, prop, value, receiver, signals);
507
+ }
508
+ });
509
+ };
510
+
511
+ /**
512
+ * Create a deeply reactive proxy for an object or array
513
+ * @param {Object|Array} obj - The object to make reactive
514
+ * @returns {Proxy} - A reactive proxy
515
+ */
516
+ const state = (obj, optionsOrName) => {
517
+ if (typeof obj !== 'object' || obj === null) return obj;
518
+
519
+ let name = typeof optionsOrName === 'string' ? optionsOrName : optionsOrName?.name;
520
+ const storage = optionsOrName?.storage;
521
+
522
+ let loadedData = null;
523
+ if (name && storage) {
524
+ try {
525
+ const item = storage.getItem(name);
526
+ if (item) loadedData = JSON.parse(item);
527
+ } catch (e) { /* ignore */ }
528
+ }
529
+
530
+ let proxy;
531
+ if (stateCache.has(obj)) {
532
+ proxy = stateCache.get(obj);
533
+ // If we have loaded data for an existing proxy, update it
534
+ if (loadedData) {
535
+ if (Array.isArray(proxy) && Array.isArray(loadedData)) {
536
+ proxy.length = 0;
537
+ proxy.push(...loadedData);
538
+ } else if (!Array.isArray(proxy) && !Array.isArray(loadedData)) {
539
+ Object.assign(proxy, loadedData);
540
+ }
541
+ }
542
+ } else {
543
+ // Apply loaded data to raw object before proxying (if no proxy yet)
544
+ if (loadedData) {
545
+ if (Array.isArray(obj) && Array.isArray(loadedData)) {
546
+ obj.length = 0;
547
+ obj.push(...loadedData);
548
+ } else if (!Array.isArray(obj) && !Array.isArray(loadedData)) {
549
+ Object.assign(obj, loadedData);
550
+ }
551
+ }
552
+
553
+ // Don't proxy objects with internal slots (RegExp, Map, Set, etc.)
554
+ const isSpecialObject = obj instanceof RegExp ||
555
+ obj instanceof Map || obj instanceof Set ||
556
+ obj instanceof WeakMap || obj instanceof WeakSet;
557
+
558
+ if (isSpecialObject) return obj;
559
+
560
+ const isArray = Array.isArray(obj);
561
+ const isDate = obj instanceof Date;
562
+ const monitor = isArray ? "length" : isDate ? "getTime" : null;
563
+
564
+ proxy = isArray || isDate ? createSpecialProxy(obj, monitor) : new Proxy(obj, {
565
+ get(target, prop, receiver) {
566
+ const signals = getOrSet(stateSignals, target, () => new Map());
567
+ return proxyGet(target, prop, receiver, signals);
568
+ },
569
+ set(target, prop, value, receiver) {
570
+ const signals = getOrSet(stateSignals, target, () => new Map());
571
+ return proxySet(target, prop, value, receiver, signals);
572
+ }
573
+ });
574
+
575
+ stateCache.set(obj, proxy);
576
+ }
577
+
578
+ if (name && storage && typeof window !== 'undefined' && window.Lightview && window.Lightview.effect) {
579
+ window.Lightview.effect(() => {
580
+ try {
581
+ const json = JSON.stringify(proxy);
582
+ storage.setItem(name, json);
583
+ } catch (e) { /* ignore */ }
584
+ });
585
+ }
586
+
587
+ if (name) {
588
+ stateRegistry.set(name, proxy);
589
+ }
590
+ return proxy;
591
+ };
592
+
593
+ state.get = (name, defaultValue) => {
594
+ if (!stateRegistry.has(name) && defaultValue !== undefined) {
595
+ return state(defaultValue, name);
596
+ }
597
+ return stateRegistry.get(name);
598
+ };
599
+
600
+ // Template literal processing: converts "${...}" strings to reactive functions
601
+ const processTemplateChild = (child, { state, signal }) => {
602
+ if (typeof child === 'string' && child.includes('${')) {
603
+ const template = child;
604
+ return () => {
605
+ try {
606
+ return new Function('state', 'signal', 'return `' + template + '`')(state, signal);
607
+ } catch (e) {
608
+ return "";
609
+ }
610
+ };
611
+ }
612
+ return child; // No transformation needed
613
+ };
614
+
615
+ const domToElements = (domNodes, element, parentTagName = null) => {
616
+ // Check if we're inside a script or style element - preserve raw content
617
+ const isRawContent = parentTagName === 'script' || parentTagName === 'style';
618
+
619
+ return domNodes.map(node => {
620
+ if (node.nodeType === Node.TEXT_NODE) {
621
+ const text = node.textContent;
622
+
623
+ // For script/style content, always return raw text
624
+ if (isRawContent) {
625
+ return text;
626
+ }
627
+
628
+ // Skip formatting whitespace/empty text nodes if they don't contain template syntax
629
+ if (!text.trim() && !text.includes('${')) return null;
630
+
631
+ if (text.includes('${')) {
632
+ return () => {
633
+ try {
634
+ const LV = window.Lightview;
635
+ return new Function('state', 'signal', 'return `' + text + '`')(LV.state, LV.signal);
636
+ } catch (e) {
637
+ return "";
638
+ }
639
+ };
640
+ }
641
+ return text;
642
+ }
643
+ if (node.nodeType !== Node.ELEMENT_NODE) return null;
644
+
645
+ const tagName = node.tagName.toLowerCase();
646
+ const attributes = {};
647
+
648
+ // Skip template processing for script/style attributes too
649
+ const skipTemplateProcessing = tagName === 'script' || tagName === 'style';
650
+
651
+ for (let attr of node.attributes) {
652
+ const value = attr.value;
653
+ if (!skipTemplateProcessing && value.includes('${')) {
654
+ attributes[attr.name] = () => {
655
+ try {
656
+ const LV = window.Lightview;
657
+ return new Function('state', 'signal', 'return `' + value + '`')(LV.state, LV.signal);
658
+ } catch (e) {
659
+ return "";
660
+ }
661
+ };
662
+ } else {
663
+ attributes[attr.name] = value;
664
+ }
665
+ }
666
+
667
+ // Pass the current tag name so children know their parent context
668
+ const children = domToElements(Array.from(node.childNodes), element, tagName);
669
+ return element(tagName, attributes, children);
670
+ }).filter(n => n !== null);
671
+ };
672
+
673
+ // WeakMap to track inserted content per element+location for deduplication
674
+ const insertedContentMap = new WeakMap();
675
+
676
+ // Simple hash function for content comparison
677
+ const hashContent = (str) => {
678
+ let hash = 0;
679
+ for (let i = 0; i < str.length; i++) {
680
+ const char = str.charCodeAt(i);
681
+ hash = ((hash << 5) - hash) + char;
682
+ hash = hash & hash; // Convert to 32bit integer
683
+ }
684
+ return hash.toString(36);
685
+ };
686
+
687
+ // Create a marker comment to identify inserted content boundaries
688
+ const createMarker = (id, isEnd = false) => {
689
+ return document.createComment(`lv-src-${isEnd ? 'end' : 'start'}:${id}`);
690
+ };
691
+
692
+
693
+ /**
694
+ * Execute scripts in a container element
695
+ * Scripts created via DOMParser or innerHTML don't execute automatically,
696
+ * so we need to replace them with new script elements to trigger execution
697
+ * @param {HTMLElement|DocumentFragment} container - Container to search for scripts
698
+ */
699
+ const executeScripts = (container) => {
700
+ if (!container) return;
701
+
702
+ // Find all script tags in the container
703
+ const scripts = container.querySelectorAll('script');
704
+
705
+ scripts.forEach(oldScript => {
706
+ // Create a new script element
707
+ const newScript = document.createElement('script');
708
+
709
+ // Copy all attributes from old to new
710
+ Array.from(oldScript.attributes).forEach(attr => {
711
+ newScript.setAttribute(attr.name, attr.value);
712
+ });
713
+
714
+ // Copy the script content
715
+ if (oldScript.src) {
716
+ // External script - src attribute already copied
717
+ newScript.src = oldScript.src;
718
+ } else {
719
+ // Inline script - copy text content
720
+ newScript.textContent = oldScript.textContent;
721
+ }
722
+
723
+ // Replace the old script with the new one
724
+ // This causes the browser to execute it
725
+ oldScript.parentNode.replaceChild(newScript, oldScript);
726
+ });
727
+ };
728
+
729
+ // Find and remove previously inserted content between markers
730
+ const removeInsertedContent = (parentEl, markerId) => {
731
+ const startMarker = `lv-src-start:${markerId}`;
732
+ const endMarker = `lv-src-end:${markerId}`;
733
+
734
+ let inRange = false;
735
+ const nodesToRemove = [];
736
+
737
+ const walker = document.createTreeWalker(
738
+ parentEl.parentElement || parentEl,
739
+ NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
740
+ null,
741
+ false
742
+ );
743
+
744
+ while (walker.nextNode()) {
745
+ const node = walker.currentNode;
746
+ if (node.nodeType === Node.COMMENT_NODE) {
747
+ if (node.textContent === startMarker) {
748
+ inRange = true;
749
+ nodesToRemove.push(node);
750
+ continue;
751
+ }
752
+ if (node.textContent === endMarker) {
753
+ nodesToRemove.push(node);
754
+ break;
755
+ }
756
+ }
757
+ if (inRange) {
758
+ nodesToRemove.push(node);
759
+ }
760
+ }
761
+
762
+ nodesToRemove.forEach(node => node.remove());
763
+ return nodesToRemove.length > 0;
764
+ };
765
+
766
+ const handleSrcAttribute = async (el, src, tagName, { element, setupChildren }) => {
767
+ // Skip standard src tags
768
+ if (STANDARD_SRC_TAGS.includes(tagName)) return;
769
+
770
+ const isPath = (s) => /^(https?:|\.|\/|\w)/.test(s) || /\.(html|json|vdom|odom)$/.test(s);
771
+
772
+ let content = null;
773
+ let isJson = false;
774
+ let isHtml = false;
775
+ let rawContent = '';
776
+
777
+ if (isPath(src)) {
778
+ try {
779
+ const url = new URL(src, document.baseURI);
780
+ const res = await fetch(url.href);
781
+ if (res.ok) {
782
+ const ext = url.pathname.split('.').pop().toLowerCase();
783
+
784
+ if (ext === 'vdom' || ext === 'odom') {
785
+ isJson = true;
786
+ } else if (ext === 'html') {
787
+ isHtml = true;
788
+ }
789
+
790
+ if (isJson) {
791
+ content = await res.json();
792
+ rawContent = JSON.stringify(content);
793
+ } else {
794
+ content = await res.text();
795
+ rawContent = content;
796
+ }
797
+ }
798
+ } catch {
799
+ // Fetch failed, try selector
800
+ }
801
+ }
802
+
803
+ let elements = [];
804
+ if (content !== null) {
805
+ if (isJson) {
806
+ elements = Array.isArray(content) ? content : [content];
807
+ } else if (isHtml) {
808
+ // Check if escape attribute is set - if so, add as escaped text instead of parsing
809
+ const shouldEscape = el.domEl.getAttribute('escape') === 'true';
810
+ if (shouldEscape) {
811
+ elements = [content];
812
+ } else {
813
+ const parser = new DOMParser();
814
+ // Remove explicit <head> content to prevent collecting metadata
815
+ // while preserving nodes that the parser auto-moves to head (e.g. styles outside head)
816
+ const contentWithoutHead = content.replace(/<head[^>]*>[\s\S]*?<\/head>/i, '');
817
+ const doc = parser.parseFromString(contentWithoutHead, 'text/html');
818
+
819
+ // Collect all resulting nodes (auto-moved head nodes + body nodes)
820
+ const allNodes = [...Array.from(doc.head.childNodes), ...Array.from(doc.body.childNodes)];
821
+ elements = domToElements(allNodes, element);
822
+ }
823
+ } else {
824
+ // Treat as text
825
+ elements = [content];
826
+ }
827
+ } else {
828
+ try {
829
+ const selected = document.querySelectorAll(src);
830
+ if (selected.length > 0) {
831
+ elements = domToElements(Array.from(selected), element);
832
+ // For selector content, create a string representation for hashing
833
+ rawContent = Array.from(selected).map(n => n.outerHTML || n.textContent).join('');
834
+ }
835
+ } catch {
836
+ // Invalid selector
837
+ }
838
+ }
839
+
840
+ if (elements.length === 0) return;
841
+
842
+ // Get location attribute (default to 'innerhtml')
843
+ const location = (el.domEl.getAttribute('location') || 'innerhtml').toLowerCase();
844
+
845
+ // Generate content hash for deduplication
846
+ const contentHash = hashContent(rawContent);
847
+ const markerId = `${location}-${contentHash.slice(0, 8)}`;
848
+
849
+ // Check if same content was already inserted
850
+ let tracking = insertedContentMap.get(el.domEl);
851
+ if (!tracking) {
852
+ tracking = {};
853
+ insertedContentMap.set(el.domEl, tracking);
854
+ }
855
+
856
+ if (tracking[location] === contentHash) {
857
+ // Same content already inserted at this location - no-op
858
+ return;
859
+ }
860
+
861
+ // Different content or first insert - remove old content if any
862
+ if (tracking[location]) {
863
+ const oldMarkerId = `${location}-${tracking[location].slice(0, 8)}`;
864
+ removeInsertedContent(el.domEl, oldMarkerId);
865
+ }
866
+
867
+ // Update tracking
868
+ tracking[location] = contentHash;
869
+
870
+ // Check for shadow DOM via location attribute
871
+ if (location === 'shadow') {
872
+ if (!el.domEl.shadowRoot) {
873
+ el.domEl.attachShadow({ mode: 'open' });
874
+ }
875
+ setupChildren(elements, el.domEl.shadowRoot);
876
+ executeScripts(el.domEl.shadowRoot);
877
+ return;
878
+ }
879
+
880
+ // Handle different location modes
881
+ switch (location) {
882
+ case 'beforebegin':
883
+ case 'afterend': {
884
+ // Insert as siblings - need to use DOM insertion
885
+ const parent = el.domEl.parentElement;
886
+ if (!parent) {
887
+ console.warn('Cannot use beforebegin/afterend without parent element');
888
+ return;
889
+ }
890
+
891
+ const fragment = document.createDocumentFragment();
892
+ fragment.appendChild(createMarker(markerId, false));
893
+
894
+ elements.forEach(childEl => {
895
+ if (typeof childEl === 'string') {
896
+ fragment.appendChild(document.createTextNode(childEl));
897
+ } else if (childEl.domEl) {
898
+ fragment.appendChild(childEl.domEl);
899
+ } else if (childEl instanceof Node) {
900
+ fragment.appendChild(childEl);
901
+ } else {
902
+ // Convert Object DOM to vDOM if needed
903
+ let vdom = childEl;
904
+ if (typeof window !== 'undefined' && window.Lightview && window.Lightview.hooks.processChild) {
905
+ vdom = window.Lightview.hooks.processChild(childEl) || childEl;
906
+ }
907
+
908
+ if (vdom.tag) {
909
+ const created = element(vdom.tag, vdom.attributes || {}, vdom.children || []);
910
+ if (created && created.domEl) {
911
+ fragment.appendChild(created.domEl);
912
+ }
913
+ }
914
+ }
915
+ });
916
+
917
+ fragment.appendChild(createMarker(markerId, true));
918
+
919
+ if (location === 'beforebegin') {
920
+ el.domEl.parentElement.insertBefore(fragment, el.domEl);
921
+ } else {
922
+ el.domEl.parentElement.insertBefore(fragment, el.domEl.nextSibling);
923
+ }
924
+ // Execute scripts after insertion
925
+ executeScripts(parent);
926
+ break;
927
+ }
928
+
929
+ case 'afterbegin': {
930
+ // Prepend to children
931
+ const fragment = document.createDocumentFragment();
932
+ fragment.appendChild(createMarker(markerId, false));
933
+
934
+ elements.forEach(childEl => {
935
+ if (typeof childEl === 'string') {
936
+ fragment.appendChild(document.createTextNode(childEl));
937
+ } else if (childEl.domEl) {
938
+ fragment.appendChild(childEl.domEl);
939
+ } else if (childEl instanceof Node) {
940
+ fragment.appendChild(childEl);
941
+ } else {
942
+ // Convert Object DOM to vDOM if needed
943
+ let vdom = childEl;
944
+ if (typeof window !== 'undefined' && window.Lightview && window.Lightview.hooks.processChild) {
945
+ vdom = window.Lightview.hooks.processChild(childEl) || childEl;
946
+ }
947
+
948
+ if (vdom.tag) {
949
+ const created = element(vdom.tag, vdom.attributes || {}, vdom.children || []);
950
+ if (created && created.domEl) {
951
+ fragment.appendChild(created.domEl);
952
+ }
953
+ }
954
+ }
955
+ });
956
+
957
+ fragment.appendChild(createMarker(markerId, true));
958
+ el.domEl.insertBefore(fragment, el.domEl.firstChild);
959
+ // Execute scripts after insertion
960
+ executeScripts(el.domEl);
961
+ break;
962
+ }
963
+
964
+ case 'beforeend': {
965
+ // Append to children
966
+ el.domEl.appendChild(createMarker(markerId, false));
967
+
968
+ elements.forEach(childEl => {
969
+ if (typeof childEl === 'string') {
970
+ el.domEl.appendChild(document.createTextNode(childEl));
971
+ } else if (childEl.domEl) {
972
+ el.domEl.appendChild(childEl.domEl);
973
+ } else if (childEl instanceof Node) {
974
+ el.domEl.appendChild(childEl);
975
+ } else {
976
+ // Convert Object DOM to vDOM if needed
977
+ let vdom = childEl;
978
+ if (typeof window !== 'undefined' && window.Lightview && window.Lightview.hooks.processChild) {
979
+ vdom = window.Lightview.hooks.processChild(childEl) || childEl;
980
+ }
981
+
982
+ if (vdom.tag) {
983
+ const created = element(vdom.tag, vdom.attributes || {}, vdom.children || []);
984
+ if (created && created.domEl) {
985
+ el.domEl.appendChild(created.domEl);
986
+ }
987
+ }
988
+ }
989
+ });
990
+
991
+ el.domEl.appendChild(createMarker(markerId, true));
992
+ // Execute scripts after insertion
993
+ executeScripts(el.domEl);
994
+ break;
995
+ }
996
+
997
+ case 'outerhtml': {
998
+ // Replace the element entirely
999
+ const parent = el.domEl.parentElement;
1000
+ if (!parent) {
1001
+ console.warn('Cannot use outerhtml without parent element');
1002
+ return;
1003
+ }
1004
+
1005
+ const fragment = document.createDocumentFragment();
1006
+ fragment.appendChild(createMarker(markerId, false));
1007
+
1008
+ elements.forEach(childEl => {
1009
+ if (typeof childEl === 'string') {
1010
+ fragment.appendChild(document.createTextNode(childEl));
1011
+ } else if (childEl.domEl) {
1012
+ fragment.appendChild(childEl.domEl);
1013
+ } else if (childEl instanceof Node) {
1014
+ fragment.appendChild(childEl);
1015
+ } else {
1016
+ // Convert Object DOM to vDOM if needed
1017
+ let vdom = childEl;
1018
+ if (typeof window !== 'undefined' && window.Lightview && window.Lightview.hooks.processChild) {
1019
+ vdom = window.Lightview.hooks.processChild(childEl) || childEl;
1020
+ }
1021
+
1022
+ if (vdom.tag) {
1023
+ const created = element(vdom.tag, vdom.attributes || {}, vdom.children || []);
1024
+ if (created && created.domEl) {
1025
+ fragment.appendChild(created.domEl);
1026
+ }
1027
+ }
1028
+ }
1029
+ });
1030
+
1031
+ fragment.appendChild(createMarker(markerId, true));
1032
+ parent.replaceChild(fragment, el.domEl);
1033
+ // Execute scripts after insertion
1034
+ executeScripts(parent);
1035
+ break;
1036
+ }
1037
+
1038
+ case 'innerhtml':
1039
+ default: {
1040
+ // Replace all children (original behavior)
1041
+ el.children = elements;
1042
+ // Execute scripts after children are set
1043
+ executeScripts(el.domEl);
1044
+ break;
1045
+ }
1046
+ }
1047
+ };
1048
+
1049
+ // Valid location values for content insertion
1050
+ const VALID_LOCATIONS = ['beforebegin', 'afterbegin', 'beforeend', 'afterend', 'innerhtml', 'outerhtml', 'shadow'];
1051
+
1052
+ // Parse position suffix from target string (e.g., "#box:afterbegin" -> { selector: "#box", location: "afterbegin" })
1053
+ const parseTargetWithLocation = (targetStr) => {
1054
+ for (const loc of VALID_LOCATIONS) {
1055
+ const suffix = ':' + loc;
1056
+ if (targetStr.toLowerCase().endsWith(suffix)) {
1057
+ return {
1058
+ selector: targetStr.slice(0, -suffix.length),
1059
+ location: loc
1060
+ };
1061
+ }
1062
+ }
1063
+ return { selector: targetStr, location: null };
1064
+ };
1065
+
1066
+ const handleNonStandardHref = (e, { domToElement, wrapDomElement }) => {
1067
+ const clickedEl = e.target.closest('[href]');
1068
+ if (!clickedEl) return;
1069
+
1070
+ const tagName = clickedEl.tagName.toLowerCase();
1071
+ if (STANDARD_HREF_TAGS.includes(tagName)) return;
1072
+
1073
+ e.preventDefault();
1074
+
1075
+ const href = clickedEl.getAttribute('href');
1076
+ const targetAttr = clickedEl.getAttribute('target');
1077
+
1078
+ // Case 1: No target attribute - existing behavior (load into self)
1079
+ if (!targetAttr) {
1080
+ let el = domToElement.get(clickedEl);
1081
+ if (!el) {
1082
+ const attrs = {};
1083
+ for (let attr of clickedEl.attributes) attrs[attr.name] = attr.value;
1084
+ el = wrapDomElement(clickedEl, tagName, attrs);
1085
+ }
1086
+ const newAttrs = { ...el.attributes, src: href };
1087
+ el.attributes = newAttrs;
1088
+ return;
1089
+ }
1090
+
1091
+ // Case 2: Target starts with _ (browser navigation)
1092
+ if (targetAttr.startsWith('_')) {
1093
+ switch (targetAttr) {
1094
+ case '_self':
1095
+ window.location.href = href;
1096
+ break;
1097
+ case '_parent':
1098
+ window.parent.location.href = href;
1099
+ break;
1100
+ case '_top':
1101
+ window.top.location.href = href;
1102
+ break;
1103
+ case '_blank':
1104
+ default:
1105
+ // _blank or any custom _name opens a new window/tab
1106
+ window.open(href, targetAttr);
1107
+ break;
1108
+ }
1109
+ return;
1110
+ }
1111
+
1112
+ // Case 3: Target is a CSS selector (with optional :position suffix)
1113
+ const { selector, location } = parseTargetWithLocation(targetAttr);
1114
+
1115
+ try {
1116
+ const targetElements = document.querySelectorAll(selector);
1117
+ targetElements.forEach(targetEl => {
1118
+ let el = domToElement.get(targetEl);
1119
+ if (!el) {
1120
+ const attrs = {};
1121
+ for (let attr of targetEl.attributes) attrs[attr.name] = attr.value;
1122
+ el = wrapDomElement(targetEl, targetEl.tagName.toLowerCase(), attrs);
1123
+ }
1124
+
1125
+ // Build new attributes
1126
+ const newAttrs = { ...el.attributes, src: href };
1127
+ if (location) {
1128
+ newAttrs.location = location;
1129
+ }
1130
+ el.attributes = newAttrs;
1131
+ });
1132
+ } catch (err) {
1133
+ console.warn('Invalid target selector:', selector, err);
1134
+ }
1135
+ };
1136
+
1137
+
1138
+
1139
+ // ============= DOM OBSERVER FOR SRC ATTRIBUTES =============
1140
+
1141
+ /**
1142
+ * Process src attribute on a DOM element that doesn't normally have src
1143
+ * @param {HTMLElement} node - DOM element to process
1144
+ * @param {Object} LV - Lightview instance
1145
+ */
1146
+ const processSrcOnNode = (node, LV) => {
1147
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
1148
+
1149
+ const tagName = node.tagName.toLowerCase();
1150
+ if (isStandardSrcTag(tagName)) return;
1151
+
1152
+ const src = node.getAttribute('src');
1153
+ if (!src) return;
1154
+
1155
+ // Get or create reactive wrapper
1156
+ let el = LV.internals.domToElement.get(node);
1157
+ if (!el) {
1158
+ const attrs = {};
1159
+ for (let attr of node.attributes) attrs[attr.name] = attr.value;
1160
+ el = LV.internals.wrapDomElement(node, tagName, attrs, []);
1161
+ }
1162
+
1163
+ handleSrcAttribute(el, src, tagName, {
1164
+ element: LV.element,
1165
+ setupChildren: LV.internals.setupChildren
1166
+ });
1167
+ };
1168
+
1169
+ // Track nodes to avoid double-processing
1170
+ const processedNodes = new WeakSet();
1171
+
1172
+ /**
1173
+ * Activate reactive syntax (${...}) in existing DOM nodes
1174
+ * Uses XPath for performance optimization
1175
+ * @param {Node} root - Root node to start scanning from
1176
+ * @param {Object} LV - Lightview instance
1177
+ */
1178
+ const activateReactiveSyntax = (root, LV) => {
1179
+ if (!root || !LV) return;
1180
+
1181
+ // Helper to compile and bind effect
1182
+ const bindEffect = (node, codeStr, isAttr = false, attrName = null) => {
1183
+ if (processedNodes.has(node) && !isAttr) return; // Skip if node fully processed (for text)
1184
+ // For attributes, we might process same element multiple times for diff attributes,
1185
+ // but the effect is per attribute so it's fine.
1186
+ // We'll mark text nodes as processed. Attributes don't strictly need it if we trust the scanner not to duplicate.
1187
+
1188
+ if (!isAttr) processedNodes.add(node);
1189
+
1190
+ try {
1191
+ // Determine if it's a single expression or a template string
1192
+ // Single expression: "${...}" with no surrounding text and only one ${
1193
+ const isSingleExpr = codeStr.trim().startsWith('${') &&
1194
+ codeStr.trim().endsWith('}') &&
1195
+ (codeStr.indexOf('${', 2) === -1);
1196
+
1197
+ let fnBody;
1198
+ if (isSingleExpr) {
1199
+ // Extract expression: remove leading ${ and trailing }
1200
+ const expr = codeStr.trim().slice(2, -1);
1201
+ fnBody = 'return ' + expr;
1202
+ } else {
1203
+ // Escape backticks and backslashes for the template literal
1204
+ const escaped = codeStr.replace(/\\/g, '\\\\').replace(/`/g, '\\`');
1205
+ fnBody = 'return `' + escaped + '`';
1206
+ }
1207
+
1208
+ const fn = new Function('state', 'signal', fnBody);
1209
+
1210
+ LV.effect(() => {
1211
+ try {
1212
+ const val = fn(LV.state, LV.signal);
1213
+ if (isAttr) {
1214
+ if (val === null || val === undefined || val === false) {
1215
+ node.removeAttribute(attrName);
1216
+ } else {
1217
+ node.setAttribute(attrName, val);
1218
+ }
1219
+ } else {
1220
+ node.textContent = val !== undefined ? val : '';
1221
+ }
1222
+ } catch (e) {
1223
+ // Silent fail
1224
+ }
1225
+ });
1226
+ } catch (e) {
1227
+ console.warn('Lightview: Failed to compile template literal', e);
1228
+ }
1229
+ };
1230
+
1231
+ // 1. Find Text Nodes containing '${'
1232
+ const textXPath = ".//text()[contains(., '${')]";
1233
+ const textResult = document.evaluate(
1234
+ textXPath,
1235
+ root,
1236
+ null,
1237
+ XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
1238
+ null
1239
+ );
1240
+
1241
+ for (let i = 0; i < textResult.snapshotLength; i++) {
1242
+ const node = textResult.snapshotItem(i);
1243
+ // Verify it's not inside a skip tag (XPath might pick them up if defined loosely)
1244
+ if (node.parentElement && node.parentElement.closest('SCRIPT, STYLE, CODE, PRE, TEMPLATE, NOSCRIPT')) continue;
1245
+ bindEffect(node, node.textContent);
1246
+ }
1247
+
1248
+ // 2. Find Elements with Attributes containing '${'
1249
+ // XPath: select any element (*) that has an attribute (@*) containing '${'
1250
+ const attrXPath = ".//*[@*[contains(., '${')]]";
1251
+ const attrResult = document.evaluate(
1252
+ attrXPath,
1253
+ root,
1254
+ null,
1255
+ XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
1256
+ null
1257
+ );
1258
+
1259
+ for (let i = 0; i < attrResult.snapshotLength; i++) {
1260
+ const element = attrResult.snapshotItem(i);
1261
+ if (['SCRIPT', 'STYLE', 'CODE', 'PRE', 'TEMPLATE', 'NOSCRIPT'].includes(element.tagName)) continue;
1262
+
1263
+ // Iterate attributes to find matches (XPath found the element, but not *which* attribute)
1264
+ Array.from(element.attributes).forEach(attr => {
1265
+ if (attr.value.includes('${')) {
1266
+ bindEffect(element, attr.value, true, attr.name);
1267
+ }
1268
+ });
1269
+ }
1270
+
1271
+ // Also check the root itself (XPath .// does not always include the context node for attributes depending on implementation details, safer to check manually if root is element)
1272
+ if (root.nodeType === Node.ELEMENT_NODE && !['SCRIPT', 'STYLE', 'CODE', 'PRE', 'TEMPLATE', 'NOSCRIPT'].includes(root.tagName)) {
1273
+ Array.from(root.attributes).forEach(attr => {
1274
+ if (attr.value.includes('${')) {
1275
+ bindEffect(root, attr.value, true, attr.name);
1276
+ }
1277
+ });
1278
+ }
1279
+ };
1280
+
1281
+ /**
1282
+ * Setup MutationObserver to watch for added nodes with src attributes OR reactive syntax
1283
+ * @param {Object} LV - Lightview instance
1284
+ */
1285
+ const setupSrcObserver = (LV) => {
1286
+ const observer = new MutationObserver((mutations) => {
1287
+ // Collect all nodes to process
1288
+ const nodesToProcess = [];
1289
+ const nodesToActivate = [];
1290
+
1291
+ for (const mutation of mutations) {
1292
+ // Handle added nodes
1293
+ if (mutation.type === 'childList') {
1294
+ for (const node of mutation.addedNodes) {
1295
+ if (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.TEXT_NODE) {
1296
+ nodesToActivate.push(node);
1297
+ }
1298
+
1299
+ if (node.nodeType !== Node.ELEMENT_NODE) continue;
1300
+
1301
+ // Check the added node itself for src
1302
+ nodesToProcess.push(node);
1303
+
1304
+ // Check descendants with src attribute
1305
+ const selector = '[src]:not(' + STANDARD_SRC_TAGS.join('):not(') + ')';
1306
+ const descendants = node.querySelectorAll(selector);
1307
+ for (const desc of descendants) {
1308
+ if (desc.tagName.toLowerCase().startsWith('lv-')) continue;
1309
+ nodesToProcess.push(desc);
1310
+ }
1311
+ }
1312
+ }
1313
+
1314
+ // Handle attribute changes
1315
+ if (mutation.type === 'attributes' && mutation.attributeName === 'src') {
1316
+ nodesToProcess.push(mutation.target);
1317
+ }
1318
+ }
1319
+
1320
+ // Batch processing
1321
+ if (nodesToProcess.length > 0 || nodesToActivate.length > 0) {
1322
+ requestAnimationFrame(() => {
1323
+ nodesToActivate.forEach(node => activateReactiveSyntax(node, LV));
1324
+ nodesToProcess.forEach(node => processSrcOnNode(node, LV));
1325
+ });
1326
+ }
1327
+ });
1328
+
1329
+ observer.observe(document.body, {
1330
+ childList: true,
1331
+ subtree: true,
1332
+ attributes: true,
1333
+ attributeFilter: ['src']
1334
+ });
1335
+
1336
+ return observer;
1337
+ };
1338
+
1339
+ // Auto-register with Lightview if available
1340
+ if (typeof window !== 'undefined' && window.Lightview) {
1341
+ const LV = window.Lightview;
1342
+
1343
+ // Extend Lightview with simple named signal getter/setter if needed (already in Core now)
1344
+ // But for template literals we use processTemplateChild which needs access to registries
1345
+ // We can just rely on LV.signal.get if it exists, or fall back
1346
+
1347
+ // Setup DOM observer for src attributes on added nodes
1348
+
1349
+ // Setup DOM observer for src attributes on added nodes
1350
+ if (document.readyState === 'loading') {
1351
+ document.addEventListener('DOMContentLoaded', () => setupSrcObserver(LV));
1352
+ } else {
1353
+ setupSrcObserver(LV);
1354
+ }
1355
+
1356
+ // Also process any existing elements
1357
+ const initialScan = () => {
1358
+ requestAnimationFrame(() => {
1359
+ activateReactiveSyntax(document.body, LV);
1360
+
1361
+ const selector = '[src]:not(' + STANDARD_SRC_TAGS.join('):not(') + ')';
1362
+ const nodes = document.querySelectorAll(selector);
1363
+ nodes.forEach(node => {
1364
+ if (node.tagName.toLowerCase().startsWith('lv-')) return;
1365
+ processSrcOnNode(node, LV);
1366
+ });
1367
+ });
1368
+ };
1369
+
1370
+ if (document.body) {
1371
+ initialScan();
1372
+ } else {
1373
+ document.addEventListener('DOMContentLoaded', initialScan);
1374
+ }
1375
+
1376
+ // Register href click handler
1377
+ LV.hooks.onNonStandardHref = (e) => {
1378
+ handleNonStandardHref(e, {
1379
+ domToElement: LV.internals.domToElement,
1380
+ wrapDomElement: LV.internals.wrapDomElement
1381
+ });
1382
+ };
1383
+
1384
+ // Extend template literal processor to existing processChild hook
1385
+ const existingProcessChild = LV.hooks.processChild;
1386
+ LV.hooks.processChild = (child) => {
1387
+ // First, use the existing hook (Object DOM conversion from lightview.js)
1388
+ if (existingProcessChild) {
1389
+ child = existingProcessChild(child) ?? child;
1390
+ }
1391
+
1392
+ // Then process template literals
1393
+ return processTemplateChild(child, {
1394
+ state: state,
1395
+ signal: LV.signal
1396
+ });
1397
+ };
1398
+ }
1399
+
1400
+
1401
+
1402
+ /**
1403
+ * Create a Custom Element class wrapper for a Lightview component
1404
+ * @param {Function} Component - The Lightview component function
1405
+ * @param {Object} options
1406
+ * @param {string} options.cssUrl - Optional URL for component CSS
1407
+ * @param {string[]} options.styles - Optional extra style URLs
1408
+ * @returns {Class} - The Custom Element class
1409
+ */
1410
+ const createCustomElement = (Component, options = {}) => {
1411
+ return class extends HTMLElement {
1412
+ constructor() {
1413
+ super();
1414
+ this.attachShadow({ mode: 'open' });
1415
+ }
1416
+
1417
+ async connectedCallback() {
1418
+ const { cssUrl, styles } = options;
1419
+
1420
+ // Create theme wrapper
1421
+ this.themeWrapper = document.createElement('div');
1422
+ this.themeWrapper.style.display = 'contents';
1423
+ // Sync theme from document
1424
+ const syncTheme = () => {
1425
+ const theme = document.documentElement.getAttribute('data-theme') || 'light';
1426
+ this.themeWrapper.setAttribute('data-theme', theme);
1427
+ };
1428
+ syncTheme();
1429
+
1430
+ // Observe theme changes
1431
+ this.themeObserver = new MutationObserver(syncTheme);
1432
+ this.themeObserver.observe(document.documentElement, {
1433
+ attributes: true,
1434
+ attributeFilter: ['data-theme']
1435
+ });
1436
+
1437
+ // Attach wrapper
1438
+ this.shadowRoot.appendChild(this.themeWrapper);
1439
+
1440
+ // Get stylesheets
1441
+ const adoptedStyleSheets = getAdoptedStyleSheets(cssUrl, styles);
1442
+
1443
+ // Handle adoptedStyleSheets
1444
+ try {
1445
+ const sheets = adoptedStyleSheets.filter(s => s instanceof CSSStyleSheet);
1446
+ this.shadowRoot.adoptedStyleSheets = sheets;
1447
+ } catch (e) {
1448
+ // Fallback handled by individual links below if needed
1449
+ }
1450
+
1451
+ // Handle link tags for strings (fallback or external non-CORS sheets)
1452
+ // Also fallback for DaisyUI if not loaded as adoptedStyleSheet
1453
+ if (!componentConfig.daisyStyleSheet) {
1454
+ const link = document.createElement('link');
1455
+ link.rel = 'stylesheet';
1456
+ link.href = DAISYUI_CDN;
1457
+ this.shadowRoot.appendChild(link);
1458
+ }
1459
+
1460
+ adoptedStyleSheets.forEach(s => {
1461
+ if (typeof s === 'string') {
1462
+ const link = document.createElement('link');
1463
+ link.rel = 'stylesheet';
1464
+ link.href = s;
1465
+ this.shadowRoot.appendChild(link);
1466
+ }
1467
+ });
1468
+
1469
+ // Define render function
1470
+ this.render = () => {
1471
+ // Collect props from attributes
1472
+ const props = {};
1473
+ for (const attr of this.attributes) {
1474
+ // Convert kebab-case to camelCase
1475
+ const name = attr.name.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
1476
+
1477
+ // Convert boolean attributes
1478
+ if (attr.value === '') {
1479
+ props[name] = true;
1480
+ } else {
1481
+ props[name] = attr.value;
1482
+ }
1483
+ }
1484
+
1485
+ // Force useShadow: false to avoid double shadow
1486
+ props.useShadow = false;
1487
+
1488
+ // Render component with a slot for children
1489
+ const slot = window.Lightview.tags.slot();
1490
+ const result = Component(props, slot);
1491
+
1492
+ // Use Lightview's internal setupChildren to render the result
1493
+ // This handles vDOM, DOM nodes, strings, and reactive content
1494
+ window.Lightview.internals.setupChildren([result], this.themeWrapper);
1495
+ };
1496
+
1497
+ // Initial render
1498
+ this.render();
1499
+
1500
+ // Observe attribute changes on self to trigger re-render
1501
+ this.attrObserver = new MutationObserver((mutations) => {
1502
+ // Only re-render if actual attributes changed
1503
+ this.render();
1504
+ });
1505
+ this.attrObserver.observe(this, {
1506
+ attributes: true
1507
+ });
1508
+ }
1509
+
1510
+ disconnectedCallback() {
1511
+ if (this.themeObserver) {
1512
+ this.themeObserver.disconnect();
1513
+ }
1514
+ if (this.attrObserver) {
1515
+ this.attrObserver.disconnect();
1516
+ }
1517
+ }
1518
+ };
1519
+ };
1520
+
1521
+ // Export for module usage
1522
+ const LightviewX = {
1523
+ state,
1524
+ themeSignal,
1525
+ setTheme,
1526
+ registerStyleSheet,
1527
+ registerThemeSheet,
1528
+ // Component initialization
1529
+ initComponents,
1530
+ componentConfig,
1531
+ shouldUseShadow,
1532
+ getAdoptedStyleSheets,
1533
+ preloadComponentCSS,
1534
+ createCustomElement
1535
+ };
1536
+
1537
+ if (typeof module !== 'undefined' && module.exports) {
1538
+ module.exports = LightviewX;
1539
+ }
1540
+ if (typeof window !== 'undefined') {
1541
+ window.LightviewX = LightviewX;
1542
+ }
1543
+
1544
+ // Initialize component hook to use Object DOM
1545
+ if (typeof window !== 'undefined') {
1546
+ // Auto-load theme
1547
+ try {
1548
+ const savedTheme = getSavedTheme();
1549
+ if (savedTheme) {
1550
+ setTheme(savedTheme);
1551
+ }
1552
+ } catch (e) { /* ignore */ }
1553
+
1554
+ window.addEventListener('load', () => {
1555
+ if (window.Lightview) {
1556
+ window.Lightview.hooks.processChild = (child) => {
1557
+ // Convert Object DOM syntax if applicable
1558
+ if (typeof child === 'object' && child !== null && !Array.isArray(child)) {
1559
+ return convertObjectDOM(child);
1560
+ }
1561
+ return child;
1562
+ };
1563
+ }
1564
+ });
1565
+
1566
+ // Immediate check in case load already fired or script is defer
1567
+ if (window.Lightview) {
1568
+ window.Lightview.hooks.processChild = (child) => {
1569
+ // Convert Object DOM syntax if applicable
1570
+ if (typeof child === 'object' && child !== null && !Array.isArray(child)) {
1571
+ return convertObjectDOM(child);
1572
+ }
1573
+ return child;
1574
+ };
1575
+ }
1576
+ }
1577
+ })();