UNPKG

lightview

Version:

A reactive UI library with features of Bau, Juris, and HTMX plus safe LLM UI generation

358 lines (310 loc) 16.4 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Lightview - Why be heavy when you can be light?</title> <meta name="description" content="Lightview is a lightweight reactive UI library. All the power. None of the weight. Heavy, complex frameworks? Lighten up."> <link rel="icon" type="image/svg+xml" href="/docs/assets/images/logo-favicon.svg"> <!-- Site Styles --> <link rel="stylesheet" href="/docs/assets/styles/site.css"> <link rel="stylesheet" href="/docs/assets/styles/themes.css"> </head> <body> <!-- Main App Shell --> <div id="app"> <!-- Navigation --> <nav id="main-nav" class="site-nav"> <div class="nav-brand"> <a href="/docs/" class="logo-link"> <img src="/docs/assets/images/logo-static.svg" alt="Lightview" class="logo"> <span class="logo-text">Lightview</span> </a> </div> <button class="nav-toggle" aria-label="Toggle navigation" onclick="toggleMobileNav()"> <span class="nav-toggle-icon"></span> </button> <div class="nav-links"> <a href="/docs/" class="nav-link">Home</a> <a href="/docs/getting-started/" class="nav-link">Get Started</a> <a href="/docs/hypermedia/" class="nav-link">Hypermedia</a> <a href="/docs/api/" class="nav-link">API</a> <a href="/docs/styles/" class="nav-link">Styles</a> <a href="/docs/components/" class="nav-link">Components</a> <a href="/docs/router/" class="nav-link">Router</a> <a href="/docs/cdom.html" class="nav-link">cDOM</a> <!--a href="/docs/playground/" class="nav-link">Playground</a>--> <a href="https://github.com/anywhichway/lightview" class="nav-link nav-link-external" target="_blank" rel="noopener"> GitHub <svg class="external-icon" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"> <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" /> </svg> </a> </div> <!-- Moved to components gallery per user request --> <div class="nav-actions"> </div> </nav> <!-- Main Content Area --> <main id="content" class="site-content"> <!-- Content loaded here via router --> <div class="loading-spinner"> <div class="spinner"></div> <p>Loading...</p> </div> </main> <!-- Footer --> <footer class="site-footer"> <div class="footer-content"> <div class="footer-brand"> <img src="/docs/assets/images/logo-static.svg" alt="Lightview" class="footer-logo"> <p class="footer-tagline">The view is beautiful from here.</p> </div> <div class="footer-links"> <div class="footer-column"> <h4>Docs</h4> <a href="/docs/getting-started/">Getting Started</a> <a href="/docs/hypermedia/">Hypermedia</a> <a href="/docs/api/">API Reference</a> <a href="/docs/styles/">Styles</a> <a href="/docs/components">Components</a> <a href="/docs/router/">Router</a> <a href="/docs/cdom.html">cDOM</a> </div> <div class="footer-column"> <h4>Resources</h4> <a href="https://github.com/anywhichway/lightview" target="_blank" rel="noopener">GitHub</a> </div> <div class="footer-column"> <h4>More</h4> <a href="/docs/about/">About</a> <a href="https://github.com/anywhichway/lightview/issues" target="_blank" rel="noopener">Report Issue</a> </div> </div> </div> <div class="footer-bottom"> <p>&copy; 2025 - 2026 Simon Y. Blackwell, AnyWhichWay, LLC. MIT License.</p> </div> </footer> </div> <!-- Lightview Core --> <script src="/lightview.js"></script> <script src="/lightview-x.js"></script> <script src="/lightview-router.js"></script> <script type="module" src="/components/navigation/breadcrumbs.js"></script> <script src="/docs/assets/js/examplify.js"></script> <!--script>const { $ } = Lightview;</script--> <!-- Router --> <script type="module"> import { localeHandler } from '/middleware/locale.js'; import { markdownHandler } from '/middleware/markdown.js'; import { notFound } from '/middleware/notFound.js'; // Mobile Navigation - Click-Outside to Dismiss const mobileNav = (() => { const getNav = () => document.querySelector('.nav-links'); const getToggle = () => document.querySelector('.nav-toggle'); let isOpening = false; const close = () => { const nav = getNav(); if (nav) nav.classList.remove('open'); }; const open = () => { const nav = getNav(); if (nav) { nav.classList.add('open'); isOpening = true; // Clear flag after opening to allow outside clicks to close setTimeout(() => { isOpening = false; }, 100); } }; const toggle = () => { const nav = getNav(); if (nav && nav.classList.contains('open')) { close(); } else { open(); } }; const init = () => { const nav = getNav(); // Close when clicking links inside nav if (nav) { nav.addEventListener('click', (e) => { if (e.target.closest('a')) close(); }); } // Close when clicking outside nav or toggle button document.addEventListener('mousedown', (event) => { const nav = getNav(); const toggle = getToggle(); if (!nav || !nav.classList.contains('open')) return; if (isOpening) return; const clickedNav = nav.contains(event.target); const clickedToggle = toggle && toggle.contains(event.target); if (!clickedNav && !clickedToggle) close(); }); }; return { toggle, close, init }; })(); // Expose globally globalThis.toggleMobileNav = mobileNav.toggle; globalThis.closeMobileNav = mobileNav.close; // Initialize mobileNav.init(); // SCROLL RESTORATION: Capture clicked link IDs document.addEventListener('click', (e) => { const link = e.target.closest('a'); if (link && link.id) { const currentState = history.state || {}; const updatedState = { ...currentState, lastClickedId: link.id }; history.replaceState(updatedState, '', location.href); } }, { capture: true }); (async function () { // Initialize components with shadow DOM enabled by default //await LightviewX.initComponents({ shadowDefault: true }); await LightviewX.registerStyleSheet('/docs/components/index.css'); await LightviewX.registerThemeSheet('/docs/assets/styles/themes.css'); const contentEl = document.getElementById('content'); const navLinks = document.querySelectorAll('.nav-link'); // Theme Selector Initializer globalThis.initThemeSelector = (container) => { if (!container) return; if (container.querySelector('select')) return; // Already initialized 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']; const currentTheme = document.documentElement.getAttribute('data-theme') || localStorage.getItem('lightview-theme') || 'light'; const select = document.createElement('select'); Object.assign(select.style, { padding: '0.25rem', borderRadius: '0.25rem', border: '1px solid #ccc', background: 'var(--b1, #fff)', color: 'var(--bc, #000)', fontSize: '0.875rem', cursor: 'pointer' }); themes.forEach(t => { const option = document.createElement('option'); option.value = t; option.textContent = t.charAt(0).toUpperCase() + t.slice(1); if (t === currentTheme) option.selected = true; select.appendChild(option); }); select.addEventListener('change', (e) => { LightviewX.setTheme(e.target.value); }); container.appendChild(select); }; // Setup Theme Selector if container exists (e.g. if we add it back to header later) initThemeSelector(document.getElementById('theme-toggle-container')); // Helper: Update active nav link function updateActiveNav(path) { navLinks.forEach(link => { const href = link.getAttribute('href'); const linkPath = href.startsWith('https://') || href.startsWith('http://') ? href : (href.startsWith('#') ? href.slice(1) : href); // Normalize paths for comparison const normalizedPath = path === '/' ? '/docs/' : path; const normalizedLinkPath = linkPath === '/' ? '/docs/' : linkPath; // For home page (/docs/), only highlight the "Home" link if (normalizedPath === '/docs/' || normalizedPath === '/docs') { if (normalizedLinkPath === '/docs/' || normalizedLinkPath === '/docs') { link.classList.add('active'); } else { link.classList.remove('active'); } } else { // For other pages, check for exact match or path prefix // Use exact match first, then check if it's a valid sub-path if (normalizedLinkPath === normalizedPath) { link.classList.add('active'); } else if (normalizedPath.startsWith(normalizedLinkPath + '/') || normalizedPath.startsWith(normalizedLinkPath + '-')) { // Match sub-paths like /docs/api matches /docs/api/elements link.classList.add('active'); } else { link.classList.remove('active'); } } }); } // Initialize Router with contentEl for automatic rendering const appRouter = LightviewRouter.router({ contentEl: contentEl, // Routes will render here automatically // On Start (Loading UI) onStart: (path) => { // Close mobile nav if open if (globalThis.closeMobileNav) globalThis.closeMobileNav(); contentEl.innerHTML = ` <div class="loading-spinner"> <div class="spinner"></div> </div> `; updateActiveNav(path); }, // On Response - called AFTER auto-render for post-render logic onResponse: (response, path) => { if (globalThis.closeMobileNav) globalThis.closeMobileNav(); // Ensure menu closes on navigation // Inject Theme Selector into Components Gallery if present const galleryHeader = contentEl.querySelector('.gallery-header'); if (galleryHeader && !galleryHeader.querySelector('#theme-toggle-container')) { const container = document.createElement('div'); container.id = 'theme-toggle-container'; container.style.marginLeft = 'auto'; galleryHeader.appendChild(container); if (globalThis.initThemeSelector) { globalThis.initThemeSelector(container); } } // Smart Scroll Restoration const restoreId = history.state?.lastClickedId; const targetEl = restoreId ? document.getElementById(restoreId) : null; // Check the destination PATH for a hash, not window.location (which might be stale) const hasHash = path.includes('#'); if (targetEl) { requestAnimationFrame(() => { targetEl.scrollIntoView({ block: 'center', behavior: 'smooth' }); }); } else if (!hasHash) { globalThis.scrollTo(0, 0); } } }); // Static Routes - automatic fetch when contentEl is set appRouter.use('/', '/docs/index.html'); appRouter.use('/docs', '/docs/index.html'); appRouter.use('/docs/index.html'); appRouter.use('/docs/getting-started', '/docs/getting-started/index.html'); appRouter.use('/docs/about/', '/docs/about.html'); appRouter.use('/docs/styles/', '/docs/styles/index.html'); appRouter.use('/docs/api/', '/docs/api/index.html'); appRouter.use('/docs/components/', '/docs/components/index.html'); appRouter.use('/docs/examples/', '/docs/examples/index.html'); appRouter.use('/docs/router/', '/docs/router.html'); appRouter.use('/docs/hypermedia/', '/docs/hypermedia/index.html'); appRouter.use('/docs/hypermedia/index.html'); appRouter.use('/docs/cdom.html'); // Wildcard Routes - path replacement with automatic fetch appRouter.use('/docs/api/*'); appRouter.use('/docs/components/*'); appRouter.use('/docs/examples/*'); // Direct HTML files appRouter.use('/docs/*.html'); // Fallback Routes (Try appending .html) appRouter.use('/docs/*', '/docs/*.html'); // 404 Handler - uses contentEl from context appRouter.use(notFound()); // Start Router appRouter.start(); })(); </script> </body> </html>