Files
michaelschiemer/resources/js/modules/livecomponent/UrlManager.js
2025-11-24 21:28:25 +01:00

151 lines
3.9 KiB
JavaScript

/**
* 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;