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.
- package/.codacy/cli.sh +149 -0
- package/.codacy/codacy.yaml +15 -0
- package/.github/instructions/codacy.instructions.md +72 -0
- package/.wranglerignore +21 -0
- package/README.md +1330 -19
- package/_headers +4 -0
- package/build.js +70 -0
- package/components/actions/button.js +151 -0
- package/components/actions/dropdown.js +120 -0
- package/components/actions/modal.js +146 -0
- package/components/actions/swap.js +118 -0
- package/components/daisyui.js +288 -0
- package/components/data-display/accordion.js +128 -0
- package/components/data-display/alert.js +112 -0
- package/components/data-display/avatar.js +170 -0
- package/components/data-display/badge.js +82 -0
- package/components/data-display/card.js +151 -0
- package/components/data-display/carousel.js +94 -0
- package/components/data-display/chart.js +220 -0
- package/components/data-display/chat.js +128 -0
- package/components/data-display/collapse.js +103 -0
- package/components/data-display/countdown.js +69 -0
- package/components/data-display/diff.js +111 -0
- package/components/data-display/kbd.js +65 -0
- package/components/data-display/loading.js +75 -0
- package/components/data-display/progress.js +79 -0
- package/components/data-display/radial-progress.js +88 -0
- package/components/data-display/skeleton.js +66 -0
- package/components/data-display/stats.js +159 -0
- package/components/data-display/table.js +146 -0
- package/components/data-display/timeline.js +146 -0
- package/components/data-display/toast.js +72 -0
- package/components/data-display/tooltip.js +74 -0
- package/components/data-input/checkbox.js +253 -0
- package/components/data-input/file-input.js +224 -0
- package/components/data-input/input.js +264 -0
- package/components/data-input/radio.js +338 -0
- package/components/data-input/range.js +204 -0
- package/components/data-input/rating.js +219 -0
- package/components/data-input/select.js +287 -0
- package/components/data-input/textarea.js +287 -0
- package/components/data-input/toggle.js +201 -0
- package/components/index.js +137 -0
- package/components/layout/divider.js +72 -0
- package/components/layout/drawer.js +142 -0
- package/components/layout/footer.js +100 -0
- package/components/layout/hero.js +109 -0
- package/components/layout/indicator.js +90 -0
- package/components/layout/join.js +78 -0
- package/components/layout/navbar.js +110 -0
- package/components/navigation/breadcrumbs.js +91 -0
- package/components/navigation/dock.js +103 -0
- package/components/navigation/menu.js +126 -0
- package/components/navigation/pagination.js +105 -0
- package/components/navigation/steps.js +89 -0
- package/components/navigation/tabs.css +177 -0
- package/components/navigation/tabs.js +123 -0
- package/components/theme/theme-switch.css +65 -0
- package/components/theme/theme-switch.js +177 -0
- package/docs/about.html +164 -0
- package/docs/api/computed.html +184 -0
- package/docs/api/effects.html +173 -0
- package/docs/api/elements.html +180 -0
- package/docs/api/enhance.html +225 -0
- package/docs/api/hypermedia.html +165 -0
- package/docs/api/index.html +178 -0
- package/docs/api/nav.html +18 -0
- package/docs/api/signals.html +136 -0
- package/docs/api/state.html +217 -0
- package/docs/assets/images/logo-favicon.svg +42 -0
- package/docs/assets/images/logo-static.svg +40 -0
- package/docs/assets/images/logo.svg +66 -0
- package/docs/assets/js/examplify.js +395 -0
- package/docs/assets/styles/site.css +1102 -0
- package/docs/assets/styles/themes.css +236 -0
- package/docs/components/accordion.html +439 -0
- package/docs/components/alert.html +528 -0
- package/docs/components/avatar.html +586 -0
- package/docs/components/badge.html +531 -0
- package/docs/components/breadcrumbs.html +278 -0
- package/docs/components/button.html +579 -0
- package/docs/components/card.html +561 -0
- package/docs/components/carousel.html +286 -0
- package/docs/components/chart-area.html +702 -0
- package/docs/components/chart-bar.html +782 -0
- package/docs/components/chart-column.html +735 -0
- package/docs/components/chart-line.html +794 -0
- package/docs/components/chart-pie.html +823 -0
- package/docs/components/chart.html +610 -15
- package/docs/components/chat.html +547 -0
- package/docs/components/checkbox.html +641 -0
- package/docs/components/collapse.html +536 -0
- package/docs/components/component-nav.html +53 -0
- package/docs/components/countdown.html +470 -0
- package/docs/components/diff.html +245 -0
- package/docs/components/divider.html +240 -0
- package/docs/components/dock.html +277 -0
- package/docs/components/drawer.html +515 -0
- package/docs/components/dropdown.html +479 -0
- package/docs/components/file-input.html +591 -0
- package/docs/components/footer.html +301 -0
- package/docs/components/gallery.html +504 -0
- package/docs/components/hero.html +264 -0
- package/docs/components/index.css +840 -0
- package/docs/components/index.html +735 -0
- package/docs/components/indicator.html +342 -0
- package/docs/components/input.html +644 -0
- package/docs/components/join.html +285 -0
- package/docs/components/kbd.html +322 -0
- package/docs/components/loading.html +521 -0
- package/docs/components/menu.html +461 -0
- package/docs/components/modal.html +639 -0
- package/docs/components/navbar.html +321 -0
- package/docs/components/pagination.html +279 -0
- package/docs/components/progress.html +514 -0
- package/docs/components/radial-progress.html +434 -0
- package/docs/components/radio.html +655 -0
- package/docs/components/range.html +611 -0
- package/docs/components/rating.html +642 -0
- package/docs/components/select.html +696 -0
- package/docs/components/sidebar-setup.js +93 -0
- package/docs/components/skeleton.html +447 -0
- package/docs/components/spinner.html +68 -0
- package/docs/components/stats.html +486 -0
- package/docs/components/steps.html +356 -0
- package/docs/components/swap.html +517 -0
- package/docs/components/switch.html +68 -0
- package/docs/components/table.html +668 -0
- package/docs/components/tabs.html +506 -0
- package/docs/components/text-input.html +68 -0
- package/docs/components/textarea.html +603 -0
- package/docs/components/timeline.html +485 -42
- package/docs/components/toast.html +474 -0
- package/docs/components/toggle.html +564 -0
- package/docs/components/tooltip.html +423 -0
- package/docs/examples/getting-started-example.html +40 -0
- package/docs/examples/index.html +93 -0
- package/docs/getting-started/index.html +739 -0
- package/docs/getting-started/reviews.html +23 -0
- package/docs/getting-started/reviews.odom +108 -0
- package/docs/getting-started/reviews.vdom +84 -0
- package/docs/index.html +132 -42
- package/docs/playground.html +416 -0
- package/docs/router.html +285 -0
- package/docs/styles/index.html +190 -0
- package/functions/_middleware.js +32 -0
- package/index.html +309 -0
- package/lightview-router.js +364 -0
- package/lightview-x.js +1577 -0
- package/lightview.js +659 -1200
- package/middleware/locale.js +25 -0
- package/middleware/markdown.js +44 -0
- package/middleware/notFound.js +37 -0
- package/package.json +27 -41
- package/watch.js +92 -0
- package/wrangler.toml +12 -0
- package/.idea/lightview.iml +0 -12
- package/.idea/modules.xml +0 -8
- package/.idea/vcs.xml +0 -6
- package/LICENSE +0 -21
- package/codepen-no-tabs-embed.css +0 -2
- package/docs/CNAME +0 -1
- package/docs/api.html +0 -674
- package/docs/blank.html +0 -10
- package/docs/comparedto.html +0 -89
- package/docs/components/chart-repl.html +0 -69
- package/docs/components/components.js +0 -113
- package/docs/components/contents.html +0 -17
- package/docs/components/gantt-repl.html +0 -61
- package/docs/components/gantt.html +0 -42
- package/docs/components/gauge-repl.html +0 -66
- package/docs/components/gauge.html +0 -20
- package/docs/components/orgchart-repl.html +0 -64
- package/docs/components/orgchart.html +0 -41
- package/docs/components/repl-as-src.html +0 -17
- package/docs/components/repl-repl.html +0 -95
- package/docs/components/repl.html +0 -527
- package/docs/components/timeline-repl.html +0 -72
- package/docs/components.html +0 -14
- package/docs/css/highlightjs.min.css +0 -9
- package/docs/css/tutorial.css +0 -35
- package/docs/examples/anchor.html +0 -11
- package/docs/examples/chart.html +0 -34
- package/docs/examples/counter.html +0 -26
- package/docs/examples/counter.test.mjs +0 -47
- package/docs/examples/counter2.html +0 -26
- package/docs/examples/directives.html +0 -79
- package/docs/examples/foreign.html +0 -50
- package/docs/examples/forgeinform.html +0 -98
- package/docs/examples/form.html +0 -61
- package/docs/examples/gauge.html +0 -18
- package/docs/examples/invalid-template-literals.html +0 -44
- package/docs/examples/medium/remote.html +0 -60
- package/docs/examples/message.html +0 -18
- package/docs/examples/nested.html +0 -11
- package/docs/examples/object-bound-form.html +0 -34
- package/docs/examples/remote-server.js +0 -51
- package/docs/examples/remote.html +0 -34
- package/docs/examples/remote.json +0 -1
- package/docs/examples/scratch.html +0 -69
- package/docs/examples/sensors/index.html +0 -44
- package/docs/examples/sensors/sensor-server.js +0 -30
- package/docs/examples/shared.html +0 -41
- package/docs/examples/template.html +0 -33
- package/docs/examples/timeline.html +0 -21
- package/docs/examples/todo.html +0 -40
- package/docs/examples/top.html +0 -10
- package/docs/examples/types.html +0 -94
- package/docs/examples/xor.html +0 -62
- package/docs/examples.html +0 -25
- package/docs/javascript/codejar.min.js +0 -8
- package/docs/javascript/highlightjs.min.js +0 -1173
- package/docs/javascript/isomorphic-git.js +0 -9
- package/docs/javascript/json5.min.js +0 -1
- package/docs/javascript/lightning-fs.js +0 -1
- package/docs/javascript/lightview.js +0 -1285
- package/docs/javascript/marked.min.js +0 -6
- package/docs/javascript/peerjs.min.js +0 -70
- package/docs/javascript/turndown.js +0 -973
- package/docs/javascript/types.js +0 -606
- package/docs/javascript/utils.js +0 -45
- package/docs/lightview.html +0 -63
- package/docs/old_index.html +0 -965
- package/docs/old_index.md +0 -1132
- package/docs/slidein.html +0 -51
- package/docs/tutorial/0-getting-started.html +0 -67
- package/docs/tutorial/1-intro-to-variables.html +0 -103
- package/docs/tutorial/10-template-components.html +0 -80
- package/docs/tutorial/11-linked-components.html +0 -76
- package/docs/tutorial/12-imported-components.html +0 -67
- package/docs/tutorial/13-input-binding.html +0 -94
- package/docs/tutorial/14-automatic-variable-creation.html +0 -74
- package/docs/tutorial/15-form-binding.html +0 -110
- package/docs/tutorial/16-if-directive.html +0 -60
- package/docs/tutorial/17-loop-directives.html +0 -83
- package/docs/tutorial/18-sanitizing-and-escaping-input.html +0 -79
- package/docs/tutorial/2-imported-and-exported-variables.html +0 -80
- package/docs/tutorial/3-data-types.html +0 -89
- package/docs/tutorial/4-extended-data-types.html +0 -83
- package/docs/tutorial/5-extended-functional-types.html +0 -96
- package/docs/tutorial/5.1-extended-functional-types.html +0 -79
- package/docs/tutorial/5.2-extended-functional-types.html +0 -70
- package/docs/tutorial/6-conventional-javascript.html +0 -75
- package/docs/tutorial/7-monitoring-with-observers.html +0 -107
- package/docs/tutorial/8-event-listeners.html +0 -65
- package/docs/tutorial/9-intro-to-components.html +0 -91
- package/docs/tutorial/contents.html +0 -32
- package/docs/tutorial/my-component.html +0 -29
- package/docs/tutorial/remote-value.json +0 -4
- package/docs/websiterepl.html +0 -46
- package/jest-puppeteer.config.js +0 -5
- package/jest.config.json +0 -12
- package/lightview.min.js +0 -1
- package/lightview_good.js +0 -1267
- package/lightview_optimized.js +0 -1274
- package/repl_hold.html +0 -320
- package/test/basic.html +0 -104
- package/test/basic.test.mjs +0 -315
- package/test/extended.html +0 -29
- package/test/extended.test.mjs +0 -448
- package/types.js +0 -607
- 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>© 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
|
+
})();
|