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
package/index.html ADDED
@@ -0,0 +1,309 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Lightview - Why be heavy when you can be light?</title>
8
+ <meta name="description"
9
+ content="Lightview is a lightweight reactive UI library. All the power. None of the weight. Heavy, complex frameworks? Lighten up.">
10
+
11
+ <link rel="icon" type="image/svg+xml" href="/docs/assets/images/logo-favicon.svg">
12
+
13
+ <!-- Site Styles -->
14
+ <link rel="stylesheet" href="/docs/assets/styles/site.css">
15
+ <link rel="stylesheet" href="/docs/assets/styles/themes.css">
16
+ </head>
17
+
18
+ <body>
19
+ <!-- Main App Shell -->
20
+ <div id="app">
21
+ <!-- Navigation -->
22
+ <nav id="main-nav" class="site-nav">
23
+ <div class="nav-brand">
24
+ <a href="/docs/" class="logo-link">
25
+ <img src="/docs/assets/images/logo-static.svg" alt="Lightview" class="logo">
26
+ <span class="logo-text">Lightview</span>
27
+ </a>
28
+ </div>
29
+ <button class="nav-toggle" aria-label="Toggle navigation" onclick="toggleMobileNav()">
30
+ <span class="nav-toggle-icon"></span>
31
+ </button>
32
+ <div class="nav-links">
33
+ <a href="/docs/" class="nav-link">Home</a>
34
+ <a href="/docs/getting-started/" class="nav-link">Get Started</a>
35
+ <a href="/docs/api/" class="nav-link">API</a>
36
+ <a href="/docs/styles/" class="nav-link">Styles</a>
37
+ <a href="/docs/components/" class="nav-link">Components</a>
38
+ <a href="/docs/router/" class="nav-link">Router</a>
39
+ <!--a href="/docs/playground/" class="nav-link">Playground</a>-->
40
+
41
+ <a href="/docs/about/" class="nav-link">About</a>
42
+ <a href="https://github.com/anywhichway/lightview" class="nav-link nav-link-external" target="_blank"
43
+ rel="noopener">
44
+ GitHub
45
+ <svg class="external-icon" viewBox="0 0 24 24" width="14" height="14" fill="none"
46
+ stroke="currentColor" stroke-width="2">
47
+ <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3" />
48
+ </svg>
49
+ </a>
50
+ </div>
51
+ <div class="nav-actions">
52
+ <div id="theme-toggle-container"></div>
53
+ </div>
54
+ </nav>
55
+
56
+ <!-- Main Content Area -->
57
+ <main id="content" class="site-content">
58
+ <!-- Content loaded here via router -->
59
+ <div class="loading-spinner">
60
+ <div class="spinner"></div>
61
+ <p>Loading...</p>
62
+ </div>
63
+ </main>
64
+
65
+ <!-- Footer -->
66
+ <footer class="site-footer">
67
+ <div class="footer-content">
68
+ <div class="footer-brand">
69
+ <img src="/docs/assets/images/logo-static.svg" alt="Lightview" class="footer-logo">
70
+ <p class="footer-tagline">The view is beautiful from here.</p>
71
+ </div>
72
+ <div class="footer-links">
73
+ <div class="footer-column">
74
+ <h4>Docs</h4>
75
+ <a href="/docs/getting-started/">Getting Started</a>
76
+ <a href="/docs/api/">API Reference</a>
77
+ <a href="/docs/components">Components</a>
78
+ </div>
79
+ <div class="footer-column">
80
+ <h4>Resources</h4>
81
+ <a href="/docs/playground/">Playground</a>
82
+
83
+ <a href="https://github.com/anywhichway/lightview" target="_blank" rel="noopener">GitHub</a>
84
+ </div>
85
+ <div class="footer-column">
86
+ <h4>More</h4>
87
+ <a href="/docs/about/">About</a>
88
+ <a href="https://github.com/anywhichway/lightview/issues" target="_blank" rel="noopener">Report
89
+ Issue</a>
90
+ </div>
91
+ </div>
92
+ </div>
93
+ <div class="footer-bottom">
94
+ <p>&copy; 2025 AnyWhichWay, LLC. MIT License.</p>
95
+ </div>
96
+ </footer>
97
+ </div>
98
+ <!-- Lightview Core -->
99
+ <script src="/lightview.js"></script>
100
+ <script src="/lightview-x.js"></script>
101
+ <script src="/lightview-router.js"></script>
102
+ <script type="module" src="/components/navigation/breadcrumbs.js"></script>
103
+ <script src="/docs/assets/js/examplify.js"></script>
104
+ <!--script>const { $ } = Lightview;</script-->
105
+
106
+ <!-- Router -->
107
+ <script type="module">
108
+ import { localeHandler } from '/middleware/locale.js';
109
+ import { markdownHandler } from '/middleware/markdown.js';
110
+ import { notFound } from '/middleware/notFound.js';
111
+
112
+ // Mobile Navigation - Click-Outside to Dismiss
113
+ const mobileNav = (() => {
114
+ const getNav = () => document.querySelector('.nav-links');
115
+ const getToggle = () => document.querySelector('.nav-toggle');
116
+ let isOpening = false;
117
+
118
+ const close = () => {
119
+ const nav = getNav();
120
+ if (nav) nav.classList.remove('open');
121
+ };
122
+
123
+ const open = () => {
124
+ const nav = getNav();
125
+ if (nav) {
126
+ nav.classList.add('open');
127
+ isOpening = true;
128
+ // Clear flag after opening to allow outside clicks to close
129
+ setTimeout(() => { isOpening = false; }, 100);
130
+ }
131
+ };
132
+
133
+ const toggle = () => {
134
+ const nav = getNav();
135
+ if (nav && nav.classList.contains('open')) {
136
+ close();
137
+ } else {
138
+ open();
139
+ }
140
+ };
141
+
142
+ const init = () => {
143
+ const nav = getNav();
144
+
145
+ // Close when clicking links inside nav
146
+ if (nav) {
147
+ nav.addEventListener('click', (e) => {
148
+ if (e.target.closest('a')) close();
149
+ });
150
+ }
151
+
152
+ // Close when clicking outside nav or toggle button
153
+ document.addEventListener('mousedown', (event) => {
154
+ const nav = getNav();
155
+ const toggle = getToggle();
156
+
157
+ if (!nav || !nav.classList.contains('open')) return;
158
+ if (isOpening) return;
159
+
160
+ const clickedNav = nav.contains(event.target);
161
+ const clickedToggle = toggle && toggle.contains(event.target);
162
+
163
+ if (!clickedNav && !clickedToggle) close();
164
+ });
165
+ };
166
+
167
+ return { toggle, close, init };
168
+ })();
169
+
170
+ // Expose globally
171
+ window.toggleMobileNav = mobileNav.toggle;
172
+ window.closeMobileNav = mobileNav.close;
173
+
174
+ // Initialize
175
+ mobileNav.init();
176
+
177
+ (async function () {
178
+ // Initialize components with shadow DOM enabled by default
179
+ //await LightviewX.initComponents({ shadowDefault: true });
180
+ await LightviewX.registerStyleSheet('/docs/components/index.css');
181
+ await LightviewX.registerThemeSheet('/docs/assets/styles/themes.css');
182
+ const contentEl = document.getElementById('content');
183
+ const navLinks = document.querySelectorAll('.nav-link');
184
+
185
+ // Setup Theme Selector
186
+ const themeContainer = document.getElementById('theme-toggle-container');
187
+ if (themeContainer) {
188
+ const themes = ['light', 'dark', 'cupcake', 'bumblebee', 'emerald', 'corporate', 'synthwave', 'retro', 'cyberpunk', 'valentine', 'halloween', 'garden', 'forest', 'aqua', 'lofi', 'pastel', 'fantasy', 'wireframe', 'black', 'luxury', 'dracula', 'cmyk', 'autumn', 'business', 'acid', 'lemonade', 'night', 'coffee', 'winter', 'dim', 'nord', 'sunset'];
189
+ // Get current from document attribute (set by lightview-x auto-loader) or storage
190
+ const currentTheme = document.documentElement.getAttribute('data-theme') || localStorage.getItem('lightview-theme') || 'light';
191
+
192
+ // Simple select styling
193
+ const select = document.createElement('select');
194
+ Object.assign(select.style, {
195
+ padding: '0.25rem',
196
+ borderRadius: '0.25rem',
197
+ border: '1px solid #ccc',
198
+ background: 'var(--b1, #fff)',
199
+ color: 'var(--bc, #000)',
200
+ fontSize: '0.875rem',
201
+ cursor: 'pointer'
202
+ });
203
+
204
+ themes.forEach(t => {
205
+ const option = document.createElement('option');
206
+ option.value = t;
207
+ option.textContent = t.charAt(0).toUpperCase() + t.slice(1);
208
+ if (t === currentTheme) option.selected = true;
209
+ select.appendChild(option);
210
+ });
211
+
212
+ select.addEventListener('change', (e) => {
213
+ LightviewX.setTheme(e.target.value);
214
+ });
215
+
216
+ themeContainer.appendChild(select);
217
+ }
218
+
219
+ // Helper: Update active nav link
220
+ function updateActiveNav(path) {
221
+ navLinks.forEach(link => {
222
+ const href = link.getAttribute('href');
223
+ const linkPath = href.startsWith('https://') || href.startsWith('http://') ? href : (href.startsWith('#') ? href.slice(1) : href);
224
+
225
+ // Normalize paths for comparison
226
+ const normalizedPath = path === '/' ? '/docs/' : path;
227
+ const normalizedLinkPath = linkPath === '/' ? '/docs/' : linkPath;
228
+
229
+ // For home page (/docs/), only highlight the "Home" link
230
+ if (normalizedPath === '/docs/' || normalizedPath === '/docs') {
231
+ if (normalizedLinkPath === '/docs/' || normalizedLinkPath === '/docs') {
232
+ link.classList.add('active');
233
+ } else {
234
+ link.classList.remove('active');
235
+ }
236
+ } else {
237
+ // For other pages, check for exact match or path prefix
238
+ // Use exact match first, then check if it's a valid sub-path
239
+ if (normalizedLinkPath === normalizedPath) {
240
+ link.classList.add('active');
241
+ } else if (normalizedPath.startsWith(normalizedLinkPath + '/') ||
242
+ normalizedPath.startsWith(normalizedLinkPath + '-')) {
243
+ // Match sub-paths like /docs/api matches /docs/api/elements
244
+ link.classList.add('active');
245
+ } else {
246
+ link.classList.remove('active');
247
+ }
248
+ }
249
+ });
250
+ }
251
+
252
+ // Initialize Router with contentEl for automatic rendering
253
+ const appRouter = LightviewRouter.router({
254
+ contentEl: contentEl, // Routes will render here automatically
255
+
256
+ // On Start (Loading UI)
257
+ onStart: (path) => {
258
+ // Close mobile nav if open
259
+ if (window.closeMobileNav) window.closeMobileNav();
260
+
261
+ contentEl.innerHTML = `
262
+ <div class="loading-spinner">
263
+ <div class="spinner"></div>
264
+ </div>
265
+ `;
266
+ updateActiveNav(path);
267
+ },
268
+
269
+ // On Response - called AFTER auto-render for post-render logic
270
+ onResponse: (response, path) => {
271
+ if (window.closeMobileNav) window.closeMobileNav(); // Ensure menu closes on navigation
272
+ window.scrollTo(0, 0);
273
+ }
274
+ });
275
+
276
+ // Static Routes - automatic fetch when contentEl is set
277
+ appRouter.use('/', '/docs/index.html');
278
+ appRouter.use('/docs', '/docs/index.html');
279
+ appRouter.use('/docs/index.html');
280
+ appRouter.use('/docs/getting-started', '/docs/getting-started/index.html');
281
+ appRouter.use('/docs/about/', '/docs/about.html');
282
+ appRouter.use('/docs/styles/', '/docs/styles/index.html');
283
+ appRouter.use('/docs/playground/', '/docs/playground.html');
284
+ appRouter.use('/docs/api/', '/docs/api/index.html');
285
+ appRouter.use('/docs/components/', '/docs/components/index.html');
286
+ appRouter.use('/docs/examples/', '/docs/examples/index.html');
287
+ appRouter.use('/docs/router/', '/docs/router.html');
288
+
289
+ // Wildcard Routes - path replacement with automatic fetch
290
+ appRouter.use('/docs/api/*');
291
+ appRouter.use('/docs/components/*');
292
+ appRouter.use('/docs/examples/*');
293
+
294
+ // Direct HTML files
295
+ appRouter.use('/docs/*.html');
296
+
297
+ // Fallback Routes (Try appending .html)
298
+ appRouter.use('/docs/*', '/docs/*.html');
299
+
300
+ // 404 Handler - uses contentEl from context
301
+ appRouter.use(notFound());
302
+
303
+ // Start Router
304
+ appRouter.start();
305
+ })();
306
+ </script>
307
+ </body>
308
+
309
+ </html>
@@ -0,0 +1,364 @@
1
+ (() => {
2
+ // ============= LIGHTVIEW ROUTER =============
3
+ // Pipeline-based History API router with middleware support
4
+
5
+ /**
6
+ * Shim function for individual pages
7
+ * Redirects direct page access to the shell with a load parameter
8
+ * @param {string} shellPath - Relative path to the shell (e.g., '/index.html')
9
+ */
10
+ const base = (shellPath) => {
11
+ if (typeof window === 'undefined') return;
12
+
13
+ // Check if we're in the shell or loaded directly
14
+ const inShell = document.getElementById('content') !== null;
15
+ if (inShell) return;
16
+
17
+ // Get current path relative to domain root
18
+ const currentPath = window.location.pathname;
19
+
20
+ // Build shell URL with load parameter
21
+ const shellUrl = new URL(shellPath, window.location.href);
22
+ shellUrl.searchParams.set('load', currentPath);
23
+
24
+ // Redirect to shell
25
+ window.location.href = shellUrl.toString();
26
+ };
27
+
28
+ /**
29
+ * Create a new Router instance
30
+ */
31
+ const router = (options = {}) => {
32
+ const {
33
+ base = '',
34
+ contentEl = null,
35
+ notFound = null,
36
+ debug = false,
37
+ onResponse = null,
38
+ onStart = null
39
+ } = options;
40
+
41
+ const chains = [];
42
+
43
+ /**
44
+ * Normalize a path by removing base and trailing slashes
45
+ */
46
+ const normalizePath = (path) => {
47
+ if (!path) return '/';
48
+
49
+ // Handle full URLs
50
+ if (path.startsWith('http') || path.startsWith('//')) {
51
+ try {
52
+ const url = new URL(path, window.location.origin);
53
+ path = url.pathname;
54
+ } catch (e) {
55
+ // Invalid URL, treat as path
56
+ }
57
+ }
58
+
59
+ if (base && path.startsWith(base)) {
60
+ path = path.slice(base.length);
61
+ }
62
+ if (!path.startsWith('/')) {
63
+ path = '/' + path;
64
+ }
65
+ if (path.length > 1 && path.endsWith('/')) {
66
+ path = path.slice(0, -1);
67
+ }
68
+ return path;
69
+ };
70
+
71
+ /**
72
+ * Convert a matcher (string/regexp) into a function
73
+ * Returns: (input) => params OR null (if no match)
74
+ */
75
+ const createMatcher = (pattern) => {
76
+ if (pattern instanceof RegExp) {
77
+ return (ctx) => {
78
+ const path = typeof ctx === 'string' ? ctx : ctx.path;
79
+ const match = path.match(pattern);
80
+ return match ? { match, ...ctx } : null;
81
+ };
82
+ }
83
+
84
+ if (typeof pattern === 'string') {
85
+ return (ctx) => {
86
+ const path = typeof ctx === 'string' ? ctx : ctx.path;
87
+
88
+ // Specific check: if pattern is exactly '*', match everything
89
+ if (pattern === '*') return { path, wildcard: path, ...ctx };
90
+
91
+ // Exact match
92
+ if (pattern === path) return { path, ...ctx };
93
+
94
+ // Wildcard /api/*
95
+ if (pattern.includes('*')) {
96
+ const regexStr = '^' + pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\\\*/g, '(.*)') + '$';
97
+ const regex = new RegExp(regexStr);
98
+ const match = path.match(regex);
99
+ if (match) {
100
+ return { path, wildcard: match[1], ...ctx };
101
+ }
102
+ }
103
+
104
+ // Named params /user/:id
105
+ if (pattern.includes(':')) {
106
+ const keys = [];
107
+ const regexStr = '^' + pattern.replace(/:([^/]+)/g, (_, key) => {
108
+ keys.push(key);
109
+ return '([^/]+)';
110
+ }) + '$';
111
+ const match = path.match(new RegExp(regexStr));
112
+
113
+ if (match) {
114
+ const params = {};
115
+ keys.forEach((key, i) => {
116
+ params[key] = match[i + 1];
117
+ });
118
+ return { path, params, ...ctx };
119
+ }
120
+ }
121
+
122
+ return null;
123
+ };
124
+ }
125
+
126
+ return pattern; // Already a function
127
+ };
128
+
129
+ /**
130
+ * Convert a replacement string into a function
131
+ * Returns: (ctx) => updated context with new path
132
+ */
133
+ const createReplacer = (pattern) => {
134
+ return (ctx) => {
135
+ let newPath = pattern;
136
+ if (ctx.wildcard && newPath.includes('*')) {
137
+ newPath = newPath.replace('*', ctx.wildcard);
138
+ }
139
+ if (ctx.params) {
140
+ Object.entries(ctx.params).forEach(([key, val]) => {
141
+ newPath = newPath.replace(':' + key, val);
142
+ });
143
+ }
144
+ // Return updated context instead of just string
145
+ return { ...ctx, path: newPath };
146
+ };
147
+ };
148
+
149
+ /**
150
+ * Default fetch handler - fetches the current path and returns Response
151
+ * Uses contentEl from context (allows middleware to override target)
152
+ */
153
+ const defaultFetchHandler = async (ctx) => {
154
+ const path = typeof ctx === 'string' ? ctx : ctx.path;
155
+ try {
156
+ const res = await fetch(path);
157
+ if (res.ok) return res;
158
+ } catch (e) {
159
+ if (debug) console.error('[Router] Fetch error:', e);
160
+ }
161
+ return null;
162
+ };
163
+
164
+ /**
165
+ * Register a route chain
166
+ * usage: router.use(pattern, replacement, handler, ...)
167
+ *
168
+ * If contentEl is set and the chain ends with a string (path) or has no handlers,
169
+ * the router automatically appends a fetch handler.
170
+ */
171
+ const use = (...args) => {
172
+ if (args.length === 0) return;
173
+ const chain = [];
174
+ const firstArg = args[0];
175
+
176
+ if (typeof firstArg !== 'function') {
177
+ chain.push(createMatcher(firstArg));
178
+ } else {
179
+ chain.push(firstArg);
180
+ }
181
+
182
+ let hasCustomHandler = false;
183
+ for (let i = 1; i < args.length; i++) {
184
+ const arg = args[i];
185
+ if (typeof arg === 'string') {
186
+ chain.push(createReplacer(arg));
187
+ } else if (typeof arg === 'function') {
188
+ chain.push(arg);
189
+ hasCustomHandler = true;
190
+ }
191
+ }
192
+
193
+ // If contentEl is set and no custom handler provided, append default fetch
194
+ if (contentEl && !hasCustomHandler) {
195
+ chain.push(defaultFetchHandler);
196
+ }
197
+
198
+ chains.push(chain);
199
+ return routerInstance;
200
+ };
201
+
202
+ /**
203
+ * Execute routing for a given path
204
+ */
205
+ const route = async (rawPath) => {
206
+ let currentPath = normalizePath(rawPath);
207
+ // Include contentEl in context for middleware to access/override
208
+ let context = { path: currentPath, contentEl };
209
+
210
+ if (debug) console.log(`[Router] Routing: ${currentPath}`);
211
+
212
+ for (const chain of chains) {
213
+ let chainResult = context;
214
+ let chainFailed = false;
215
+
216
+ for (const fn of chain) {
217
+ try {
218
+ const result = await fn(chainResult);
219
+
220
+ if (result instanceof Response) return result;
221
+ if (!result) {
222
+ chainFailed = true;
223
+ break;
224
+ }
225
+
226
+ chainResult = result;
227
+ } catch (err) {
228
+ console.error('[Router] Error in route chain:', err);
229
+ chainFailed = true;
230
+ break;
231
+ }
232
+ }
233
+
234
+ if (!chainFailed) {
235
+ // Fallthrough with updated context
236
+ if (typeof chainResult === 'string') {
237
+ context = { path: chainResult, contentEl };
238
+ if (debug) console.log(`[Router] Path updated to: ${chainResult}`);
239
+ } else if (chainResult && chainResult.path) {
240
+ context = chainResult;
241
+ // Ensure contentEl is preserved if not in result
242
+ if (!context.contentEl) context.contentEl = contentEl;
243
+ }
244
+ }
245
+ }
246
+
247
+ if (notFound) return notFound(context);
248
+ return null;
249
+ };
250
+
251
+ const handleRequest = async (path) => {
252
+ if (onStart) onStart(path);
253
+
254
+ const response = await route(path);
255
+
256
+ if (!response) {
257
+ console.warn(`[Router] No route handled path: ${path}`);
258
+ return null;
259
+ }
260
+
261
+ // Auto-render to contentEl if provided and response is OK
262
+ if (response.ok && contentEl) {
263
+ const html = await response.text();
264
+ contentEl.innerHTML = html;
265
+
266
+ // Re-execute scripts in the loaded content
267
+ const scripts = contentEl.querySelectorAll('script');
268
+ scripts.forEach(script => {
269
+ const newScript = document.createElement('script');
270
+ if (script.type) newScript.type = script.type;
271
+ if (script.src) {
272
+ newScript.src = script.src;
273
+ } else {
274
+ newScript.textContent = script.textContent;
275
+ }
276
+ script.parentNode.replaceChild(newScript, script);
277
+ });
278
+ }
279
+
280
+ // Call onResponse AFTER auto-render for post-render logic (analytics, scroll, etc.)
281
+ if (onResponse) {
282
+ await onResponse(response, path);
283
+ }
284
+
285
+ return response;
286
+ };
287
+
288
+ const navigate = (path) => {
289
+ path = normalizePath(path);
290
+ let fullPath = base + path;
291
+ return handleRequest(fullPath).then((response) => {
292
+ let dest = response?.url;
293
+ if (dest && (dest.startsWith('http') || dest.startsWith('//'))) {
294
+ try {
295
+ const u = new URL(dest, window.location.origin);
296
+ dest = u.pathname + u.search + u.hash;
297
+ } catch (e) { }
298
+ }
299
+ // Fallback to intent if response has no URL
300
+ if (!dest) dest = fullPath;
301
+ window.history.pushState({ path: dest }, '', dest);
302
+ }).catch((err) => {
303
+ console.error('[Router] Error handling request:', err);
304
+ })
305
+ };
306
+
307
+ const start = async () => {
308
+ const urlParams = new URLSearchParams(window.location.search);
309
+ const loadPath = urlParams.get('load');
310
+
311
+ window.addEventListener('popstate', (e) => {
312
+ const path = e.state?.path || normalizePath(window.location.pathname);
313
+ handleRequest(path);
314
+ });
315
+
316
+ document.addEventListener('click', (e) => {
317
+ const link = e.target.closest('a[href]');
318
+ if (!link) return;
319
+ const href = link.getAttribute('href');
320
+ if (
321
+ !href || href.startsWith('http') || href.startsWith('//') ||
322
+ href.startsWith('#') || href.startsWith('mailto:') ||
323
+ link.target === '_blank'
324
+ ) return;
325
+
326
+ e.preventDefault();
327
+ const url = new URL(href, document.baseURI);
328
+ const path = normalizePath(url.pathname);
329
+ navigate(path);
330
+ });
331
+
332
+ if (loadPath) {
333
+ window.history.replaceState({ path: loadPath }, '', loadPath);
334
+ handleRequest(loadPath);
335
+ } else {
336
+ const initialPath = normalizePath(window.location.pathname);
337
+ window.history.replaceState({ path: initialPath }, '', base + initialPath);
338
+ handleRequest(initialPath);
339
+ }
340
+
341
+ return routerInstance;
342
+ };
343
+
344
+ const routerInstance = {
345
+ use,
346
+ navigate,
347
+ start
348
+ };
349
+
350
+ return routerInstance;
351
+ };
352
+
353
+ const LightviewRouter = {
354
+ base,
355
+ router
356
+ };
357
+
358
+ if (typeof module !== 'undefined' && module.exports) {
359
+ module.exports = LightviewRouter;
360
+ }
361
+ if (typeof window !== 'undefined') {
362
+ window.LightviewRouter = LightviewRouter;
363
+ }
364
+ })();