// modules/core/click-manager.js import { Logger } from './logger.js'; import { useEvent } from './useEvent.js'; import { navigateTo } from "./navigateTo"; import { LinkPrefetcher } from './LinkPrefetcher.js'; let callback = null; let unsubscribes = []; let prefetcher = null; function isInternal(link) { return link.origin === location.origin; } function handleClick(e) { const link = e.target.closest('a'); if (!link || e.defaultPrevented) return; const href = link.getAttribute('href'); if (!href || href.startsWith('#')) return; // Skip conditions if ( link.target === '_blank' || link.hasAttribute('download') || link.getAttribute('rel')?.includes('external') || link.hasAttribute('data-skip') ) { Logger.info(`[click-manager] skipped: ${href}`); return; } if (isInternal(link)) { e.preventDefault(); const cached = prefetcher ? prefetcher.getCached(href) : null; const options = { viewTransition: link.hasAttribute('data-view-transition'), replace: link.hasAttribute('data-replace'), modal: link.hasAttribute('data-modal'), prefetched: cached !== null, data: cached, }; Logger.info(`[click-manager] internal: ${href}`, options); if(options.modal) { callback?.(href, link, options); } else { navigateTo(href, options); } } } function handleMouseOver(e) { const link = e.target.closest('a[href]'); if (!link || !prefetcher) return; prefetcher.handleHover(link); } function handleMouseOut(e) { const link = e.target.closest('a[href]'); if (!link || !prefetcher) return; prefetcher.handleMouseLeave(); } function handlePopState() { const href = location.pathname; Logger.info(`[click-manager] popstate: ${href}`); navigateTo(href, { replace: true }); } export function getPrefetched(href) { return prefetcher ? prefetcher.getCached(href) : null; } export function prefetchHref(href) { if (!href || !prefetcher) return; prefetcher.prefetch(href, { priority: 'high' }); } export function prefetchUrls(urls) { if (!prefetcher) return; prefetcher.prefetchEager(urls); } export function init(onNavigate, prefetchOptions = {}) { callback = onNavigate; // Initialize prefetcher with options prefetcher = new LinkPrefetcher({ strategies: ['hover', 'visible'], hoverDelay: 150, observerMargin: '50px', maxCacheSize: 20, cacheTTL: 60000, ...prefetchOptions }); unsubscribes = [ useEvent(document, 'click', handleClick), useEvent(document, 'mouseover', handleMouseOver), useEvent(document, 'mouseout', handleMouseOut), useEvent(window, 'popstate', handlePopState), ]; // Observe all initial links if visible strategy is enabled if (prefetchOptions.strategies?.includes('visible') !== false) { // Wait for DOM to be ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { prefetcher.observeLinks(); }); } else { prefetcher.observeLinks(); } } // Support for eager prefetching via data attributes document.querySelectorAll('a[data-prefetch="eager"]').forEach(link => { const href = link.getAttribute('href'); if (href) { prefetcher.prefetch(href, { priority: 'high' }); } }); Logger.info('[click-manager] ready with prefetching'); } export function destroy() { callback = null; if (prefetcher) { prefetcher.destroy(); prefetcher = null; } unsubscribes.forEach(unsub => unsub()); unsubscribes = []; Logger.info('[click-manager] destroyed'); } // Export prefetcher for advanced usage export function getPrefetcher() { return prefetcher; }