/** * URL Manager for LiveComponents * * Handles URL updates without page reload: * - data-lc-push-url: Push URL to browser history * - data-lc-replace-url: Replace URL without history entry * - Browser Back/Forward support * - SPA Router coordination */ export class UrlManager { constructor() { this.initialized = false; this.popStateHandler = null; } /** * Initialize URL manager */ init() { if (this.initialized) { console.warn('[UrlManager] Already initialized'); return; } // Setup popstate handler for browser back/forward this.popStateHandler = (event) => { this.handlePopState(event); }; window.addEventListener('popstate', this.popStateHandler); this.initialized = true; console.log('[UrlManager] Initialized'); } /** * Push URL to browser history * * @param {string} url - URL to push * @param {string} title - Optional page title */ pushUrl(url, title = '') { try { window.history.pushState({ url, title }, title || document.title, url); // Update document title if provided if (title) { document.title = title; } // Dispatch custom event window.dispatchEvent(new CustomEvent('lc:url:pushed', { detail: { url, title } })); console.log(`[UrlManager] Pushed URL: ${url}`); } catch (error) { console.error('[UrlManager] Failed to push URL:', error); } } /** * Replace URL without history entry * * @param {string} url - URL to replace * @param {string} title - Optional page title */ replaceUrl(url, title = '') { try { window.history.replaceState({ url, title }, title || document.title, url); // Update document title if provided if (title) { document.title = title; } // Dispatch custom event window.dispatchEvent(new CustomEvent('lc:url:replaced', { detail: { url, title } })); console.log(`[UrlManager] Replaced URL: ${url}`); } catch (error) { console.error('[UrlManager] Failed to replace URL:', error); } } /** * Handle browser back/forward navigation * * @param {PopStateEvent} event - PopState event */ handlePopState(event) { const state = event.state; if (state && state.url) { // Dispatch custom event for popstate window.dispatchEvent(new CustomEvent('lc:url:popstate', { detail: { url: state.url, state } })); // If SPA Router is available, let it handle navigation if (window.SPARouter && typeof window.SPARouter.navigate === 'function') { window.SPARouter.navigate(state.url); return; } // Otherwise, trigger a page reload or custom handling // This is a fallback - in most cases, SPA Router should handle it console.log('[UrlManager] PopState detected, URL:', state.url); } } /** * Get current URL * * @returns {string} - Current URL */ getCurrentUrl() { return window.location.href; } /** * Get current path * * @returns {string} - Current path */ getCurrentPath() { return window.location.pathname; } /** * Cleanup URL manager */ cleanup() { if (this.popStateHandler) { window.removeEventListener('popstate', this.popStateHandler); this.popStateHandler = null; } this.initialized = false; } } // Create singleton instance export const urlManager = new UrlManager(); export default urlManager;