@coherent.js/client 1.1.1 → 2.0.0-rc.0
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/README.md +126 -66
- package/dist/chunk-EAOAAY2X.js +1550 -0
- package/dist/chunk-EAOAAY2X.js.map +7 -0
- package/dist/{chunk-N36BSMJU.js → chunk-UYL3RRRC.js} +134 -32
- package/dist/chunk-UYL3RRRC.js.map +7 -0
- package/dist/events/index.js +1 -1
- package/dist/hmr.js +34 -4
- package/dist/hmr.js.map +3 -3
- package/dist/index.js +598 -1732
- package/dist/index.js.map +4 -4
- package/dist/router.js +209 -31
- package/dist/router.js.map +2 -2
- package/package.json +4 -1
- package/types/hmr.d.ts +32 -1
- package/types/index.d.ts +258 -701
- package/types/router.d.ts +34 -11
- package/dist/chunk-N36BSMJU.js.map +0 -7
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/hmr/cleanup-tracker.js", "../src/hmr/state-capturer.js", "../src/hmr/overlay.js", "../src/hmr/indicator.js", "../src/hmr/module-tracker.js", "../src/hmr/client.js"],
|
|
4
|
+
"sourcesContent": ["/**\n * CleanupTracker - Tracks and automatically cleans up module resources during HMR\n *\n * Prevents memory leaks by tracking timers, intervals, event listeners, and fetch\n * requests created by modules. When a module is disposed during HMR, all its\n * tracked resources are automatically cleaned up.\n *\n * @module @coherent.js/client/hmr/cleanup-tracker\n */\n\n/**\n * A signal that aborts when either the tracker's controller or the caller's\n * signal does, keeping the caller's abort reason.\n *\n * @param {AbortController} controller - Tracker-owned controller\n * @param {AbortSignal} [callerSignal] - Signal the caller passed to fetch\n * @returns {AbortSignal}\n */\nfunction mergeSignals(controller, callerSignal) {\n if (!callerSignal) {\n return controller.signal;\n }\n if (typeof AbortSignal !== 'undefined' && typeof AbortSignal.any === 'function') {\n return AbortSignal.any([controller.signal, callerSignal]);\n }\n if (callerSignal.aborted) {\n controller.abort(callerSignal.reason);\n } else {\n callerSignal.addEventListener('abort', () => controller.abort(callerSignal.reason), { once: true });\n }\n return controller.signal;\n}\n\n/**\n * Tracks module resources for cleanup during HMR\n */\nexport class CleanupTracker {\n constructor() {\n /**\n * Per-module resource tracking\n * @type {Map<string, { timers: Set<number>, intervals: Set<number>, listeners: Array, abortControllers: Set<AbortController> }>}\n */\n this.moduleResources = new Map();\n }\n\n /**\n * Create a tracked context for a module\n *\n * Returns an object with tracked versions of setTimeout, setInterval,\n * addEventListener, and fetch that automatically clean up on module disposal.\n *\n * @param {string} moduleId - Unique identifier for the module\n * @returns {Object} Tracked context with setTimeout, setInterval, etc.\n */\n createContext(moduleId) {\n const resources = {\n timers: new Set(),\n intervals: new Set(),\n listeners: [],\n abortControllers: new Set(),\n };\n\n this.moduleResources.set(moduleId, resources);\n\n const context = {\n /**\n * Tracked setTimeout - auto-removes from tracking on completion\n * @param {Function} callback - Function to execute\n * @param {number} delay - Delay in milliseconds\n * @param {...*} args - Additional arguments to pass to callback\n * @returns {number} Timer ID\n */\n setTimeout: (callback, delay, ...args) => {\n const id = setTimeout(\n (...a) => {\n resources.timers.delete(id);\n callback(...a);\n },\n delay,\n ...args\n );\n resources.timers.add(id);\n return id;\n },\n\n /**\n * Tracked setInterval - stores in intervals set until cleared\n * @param {Function} callback - Function to execute\n * @param {number} delay - Interval in milliseconds\n * @param {...*} args - Additional arguments to pass to callback\n * @returns {number} Interval ID\n */\n setInterval: (callback, delay, ...args) => {\n const id = setInterval(callback, delay, ...args);\n resources.intervals.add(id);\n return id;\n },\n\n /**\n * Clear a tracked timeout\n * @param {number} id - Timer ID to clear\n */\n clearTimeout: (id) => {\n resources.timers.delete(id);\n clearTimeout(id);\n },\n\n /**\n * Clear a tracked interval\n * @param {number} id - Interval ID to clear\n */\n clearInterval: (id) => {\n resources.intervals.delete(id);\n clearInterval(id);\n },\n\n /**\n * Tracked addEventListener - stores listener info for removal on cleanup\n * @param {EventTarget} target - Element or object to attach listener to\n * @param {string} event - Event type\n * @param {Function} handler - Event handler function\n * @param {Object|boolean} [options] - Listener options\n */\n addEventListener: (target, event, handler, options) => {\n target.addEventListener(event, handler, options);\n resources.listeners.push({ target, event, handler, options });\n },\n\n /**\n * Create a tracked AbortController\n * @returns {AbortController} Tracked AbortController\n */\n createAbortController: () => {\n const controller = new AbortController();\n resources.abortControllers.add(controller);\n return controller;\n },\n\n /**\n * Tracked fetch - creates AbortController automatically, cleans up on completion\n * @param {string|URL} url - URL to fetch\n * @param {Object} [options] - Fetch options\n * @returns {Promise<Response>} Fetch promise\n */\n fetch: (url, options = {}) => {\n const controller = new AbortController();\n resources.abortControllers.add(controller);\n\n // Abort on module disposal and when the caller's own signal aborts\n const signal = mergeSignals(controller, options.signal);\n\n return fetch(url, { ...options, signal }).finally(() => {\n resources.abortControllers.delete(controller);\n });\n },\n };\n\n return context;\n }\n\n /**\n * Cleanup all resources for a module\n *\n * Called during HMR module disposal. Clears all timers, intervals,\n * removes all event listeners, and aborts all pending fetch requests.\n *\n * @param {string} moduleId - Module identifier to clean up\n */\n cleanup(moduleId) {\n const resources = this.moduleResources.get(moduleId);\n if (!resources) return;\n\n // Clear all timers\n for (const id of resources.timers) {\n clearTimeout(id);\n }\n resources.timers.clear();\n\n // Clear all intervals\n for (const id of resources.intervals) {\n clearInterval(id);\n }\n resources.intervals.clear();\n\n // Remove all event listeners\n for (const { target, event, handler, options } of resources.listeners) {\n try {\n target.removeEventListener(event, handler, options);\n } catch {\n // Target may no longer exist\n }\n }\n resources.listeners.length = 0;\n\n // Abort all pending fetches\n for (const controller of resources.abortControllers) {\n try {\n controller.abort();\n } catch {\n // Ignore abort errors\n }\n }\n resources.abortControllers.clear();\n\n this.moduleResources.delete(moduleId);\n }\n\n /**\n * Check for potential resource leaks (for development mode)\n *\n * Logs warnings if resources weren't cleaned up before module disposal.\n * Call this before cleanup() to detect potential leaks.\n *\n * @param {string} moduleId - Module identifier to check\n */\n checkForLeaks(moduleId) {\n const resources = this.moduleResources.get(moduleId);\n if (!resources) return;\n\n const warnings = [];\n\n if (resources.timers.size > 0) {\n warnings.push(`${resources.timers.size} timer(s) not cleaned up`);\n }\n if (resources.intervals.size > 0) {\n warnings.push(`${resources.intervals.size} interval(s) not cleaned up`);\n }\n if (resources.listeners.length > 0) {\n warnings.push(`${resources.listeners.length} listener(s) not cleaned up`);\n }\n if (resources.abortControllers.size > 0) {\n warnings.push(\n `${resources.abortControllers.size} pending fetch(es) not aborted`\n );\n }\n\n if (warnings.length > 0) {\n console.warn(`[HMR] Potential leak in module ${moduleId}: ${warnings.join(', ')}`);\n }\n }\n\n /**\n * Check if a module has tracked resources\n * @param {string} moduleId - Module identifier\n * @returns {boolean} True if module has resources\n */\n hasResources(moduleId) {\n return this.moduleResources.has(moduleId);\n }\n\n /**\n * Get resource counts for a module (for testing/debugging)\n * @param {string} moduleId - Module identifier\n * @returns {Object|null} Resource counts or null if module not tracked\n */\n getResourceCounts(moduleId) {\n const resources = this.moduleResources.get(moduleId);\n if (!resources) return null;\n\n return {\n timers: resources.timers.size,\n intervals: resources.intervals.size,\n listeners: resources.listeners.length,\n abortControllers: resources.abortControllers.size,\n };\n }\n}\n\n/**\n * Singleton cleanup tracker instance\n * @type {CleanupTracker}\n */\nexport const cleanupTracker = new CleanupTracker();\n", "/**\n * StateCapturer - Captures and restores form input state and scroll positions during HMR\n *\n * Preserves user input and scroll positions across hot module replacement updates,\n * providing a seamless development experience. Only restores scroll position if\n * the page layout hasn't changed significantly.\n *\n * @module @coherent.js/client/hmr/state-capturer\n */\n\n/**\n * Captures and restores form/scroll state during HMR\n */\nexport class StateCapturer {\n constructor() {\n /**\n * Captured form input states\n * @type {Map<string, { value: string, type: string, selectionStart?: number, selectionEnd?: number, checked?: boolean }>}\n */\n this.capturedInputs = new Map();\n\n /**\n * Captured scroll positions\n * @type {Map<string, { top: number, left: number }>}\n */\n this.scrollPositions = new Map();\n\n /**\n * Layout snapshot for change detection\n * @type {Object|null}\n */\n this.layoutSnapshot = null;\n }\n\n /**\n * Generate a stable key for an input element\n *\n * Uses multiple factors to identify inputs across HMR updates:\n * 1. ID (most stable)\n * 2. Name + type (+ value for radios and checkboxes)\n * 3. Form context\n * 4. DOM path (fallback)\n *\n * @param {HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement} input - Input element\n * @returns {string} Stable key for the input\n */\n getInputKey(input) {\n const parts = [];\n\n // ID is most stable\n if (input.id) {\n parts.push(`#${input.id}`);\n return parts.join(':');\n }\n\n // Name + type combination\n if (input.name) {\n parts.push(`[name=\"${input.name}\"]`);\n }\n\n if (input.type) {\n parts.push(`[type=\"${input.type}\"]`);\n }\n\n // Radios (and checkbox groups) share a name: tell them apart by value,\n // or every button of a group collapses onto one key\n if (input.name && (input.type === 'radio' || input.type === 'checkbox')) {\n parts.push(`[value=\"${input.value}\"]`);\n }\n\n // Form context if available\n if (input.form?.id) {\n parts.push(`form#${input.form.id}`);\n }\n\n // Fallback: DOM path\n if (parts.length === 0) {\n parts.push(this.getElementPath(input));\n }\n\n return parts.join(':');\n }\n\n /**\n * Build a CSS-like path for an element\n *\n * @param {HTMLElement} element - Element to build path for\n * @returns {string} CSS-like path (e.g., \"form > div:nth-of-type(2) > input\")\n */\n getElementPath(element) {\n const path = [];\n let current = element;\n\n while (current && current !== document.body && path.length < 10) {\n let selector = current.tagName.toLowerCase();\n\n // Add class names (limited to avoid overly long selectors)\n if (current.className && typeof current.className === 'string') {\n const classes = current.className.trim().split(/\\s+/).slice(0, 2);\n if (classes.length > 0 && classes[0]) {\n selector += `.${classes.join('.')}`;\n }\n }\n\n // Add nth-of-type for disambiguation\n if (current.parentElement) {\n const siblings = current.parentElement.querySelectorAll(\n `:scope > ${current.tagName.toLowerCase()}`\n );\n if (siblings.length > 1) {\n const index = Array.from(siblings).indexOf(current);\n selector += `:nth-of-type(${index + 1})`;\n }\n }\n\n path.unshift(selector);\n current = current.parentElement;\n }\n\n return path.join(' > ');\n }\n\n /**\n * Capture all form input states\n *\n * Iterates through all input, textarea, and select elements,\n * capturing their values, selection state, and checked state.\n *\n * @returns {Map<string, Object>} Map of input keys to their captured state\n */\n captureFormState() {\n this.capturedInputs.clear();\n\n const inputs = document.querySelectorAll('input, textarea, select');\n\n for (const input of inputs) {\n const key = this.getInputKey(input);\n const state = {\n value: input.value,\n type: input.type || input.tagName.toLowerCase(),\n };\n\n // Capture selection for text-like inputs\n if (\n typeof input.selectionStart === 'number' &&\n (input.type === 'text' ||\n input.type === 'search' ||\n input.type === 'url' ||\n input.type === 'tel' ||\n input.type === 'password' ||\n input.tagName.toLowerCase() === 'textarea')\n ) {\n state.selectionStart = input.selectionStart;\n state.selectionEnd = input.selectionEnd;\n }\n\n // Capture checked state for checkboxes/radios\n if (input.type === 'checkbox' || input.type === 'radio') {\n state.checked = input.checked;\n }\n\n this.capturedInputs.set(key, state);\n }\n\n return this.capturedInputs;\n }\n\n /**\n * Restore form input states after HMR update\n *\n * Finds inputs by their captured keys and restores their values,\n * only if the input type matches (to avoid corrupting data).\n */\n restoreFormState() {\n for (const [key, state] of this.capturedInputs) {\n const inputs = this.findInputsByKey(key);\n\n for (const input of inputs) {\n // Only restore if type matches\n const currentType = input.type || input.tagName.toLowerCase();\n if (currentType !== state.type) {\n continue;\n }\n\n // Restore checkbox/radio checked state\n if (state.checked !== undefined) {\n input.checked = state.checked;\n continue;\n }\n\n // Restore value\n input.value = state.value;\n\n // Restore selection if applicable and input is not focused\n if (\n state.selectionStart !== undefined &&\n document.activeElement !== input\n ) {\n try {\n input.setSelectionRange(state.selectionStart, state.selectionEnd);\n } catch {\n // Some input types don't support setSelectionRange\n }\n }\n }\n }\n }\n\n /**\n * Find inputs matching a captured key\n *\n * @param {string} key - Captured input key\n * @returns {HTMLElement[]} Array of matching input elements\n */\n findInputsByKey(key) {\n // ID-based key\n if (key.startsWith('#')) {\n const id = key.slice(1);\n const el = document.getElementById(id);\n return el ? [el] : [];\n }\n\n // Name-based key\n const nameMatch = key.match(/\\[name=\"([^\"]+)\"\\]/);\n if (nameMatch) {\n const name = nameMatch[1];\n const typeMatch = key.match(/\\[type=\"([^\"]+)\"\\]/);\n const type = typeMatch ? typeMatch[1] : null;\n\n let selector = `[name=\"${name}\"]`;\n if (type) {\n selector += `[type=\"${type}\"]`;\n }\n\n const inputs = Array.from(document.querySelectorAll(selector));\n const valueMatch = key.match(/\\[value=\"([^\"]*)\"\\]/);\n return valueMatch ? inputs.filter((input) => input.value === valueMatch[1]) : inputs;\n }\n\n // Path-based key - try to query directly\n try {\n const el = document.querySelector(key);\n return el ? [el] : [];\n } catch {\n return [];\n }\n }\n\n /**\n * Capture scroll positions for window and scrollable containers\n *\n * Captures scroll positions for:\n * - Window (scrollX/scrollY)\n * - Elements with [data-coherent-scroll-preserve] attribute\n * - Elements with overflow that have actual scrolling\n */\n captureScrollPositions() {\n this.scrollPositions.clear();\n\n // Window scroll\n this.scrollPositions.set('window', {\n top: window.scrollY,\n left: window.scrollX,\n });\n\n // Find scrollable containers with explicit marker\n const markedScrollables = document.querySelectorAll(\n '[data-coherent-scroll-preserve]'\n );\n for (const el of markedScrollables) {\n const key = this.getScrollableKey(el);\n this.scrollPositions.set(key, {\n top: el.scrollTop,\n left: el.scrollLeft,\n });\n }\n\n // Find elements with overflow that have actual scroll content\n const overflowElements = document.querySelectorAll(\n '[style*=\"overflow\"], [class]'\n );\n for (const el of overflowElements) {\n const style = window.getComputedStyle(el);\n const hasOverflow =\n style.overflow === 'auto' ||\n style.overflow === 'scroll' ||\n style.overflowY === 'auto' ||\n style.overflowY === 'scroll' ||\n style.overflowX === 'auto' ||\n style.overflowX === 'scroll';\n\n if (\n hasOverflow &&\n (el.scrollHeight > el.clientHeight || el.scrollWidth > el.clientWidth)\n ) {\n const key = this.getScrollableKey(el);\n if (!this.scrollPositions.has(key)) {\n this.scrollPositions.set(key, {\n top: el.scrollTop,\n left: el.scrollLeft,\n });\n }\n }\n }\n\n return this.scrollPositions;\n }\n\n /**\n * Generate a stable key for a scrollable element\n *\n * @param {HTMLElement} el - Scrollable element\n * @returns {string} Key for the element\n */\n getScrollableKey(el) {\n if (el.id) {\n return `#${el.id}`;\n }\n\n const component = el.getAttribute('data-coherent-component');\n if (component) {\n return `[data-coherent-component=\"${component}\"]`;\n }\n\n return this.getElementPath(el);\n }\n\n /**\n * Capture layout snapshot for change detection\n *\n * Captures body dimensions and positions of anchor elements\n * (elements with data-coherent-component attribute).\n */\n captureLayout() {\n this.layoutSnapshot = {\n bodyHeight: document.body.scrollHeight,\n bodyWidth: document.body.scrollWidth,\n anchors: new Map(),\n };\n\n // Capture positions of component elements as anchors\n const components = document.querySelectorAll('[data-coherent-component]');\n for (const el of components) {\n const rect = el.getBoundingClientRect();\n const key = this.getScrollableKey(el);\n this.layoutSnapshot.anchors.set(key, {\n top: rect.top,\n left: rect.left,\n width: rect.width,\n height: rect.height,\n });\n }\n }\n\n /**\n * Check if layout changed significantly (>50px shift)\n *\n * Returns true if:\n * - Body dimensions changed by more than 50px\n * - Any anchor element position shifted by more than 50px\n *\n * @returns {boolean} True if layout changed significantly\n */\n layoutChangedSignificantly() {\n if (!this.layoutSnapshot) {\n return false;\n }\n\n const THRESHOLD = 50; // pixels\n\n // Check body dimensions\n const heightDiff = Math.abs(\n document.body.scrollHeight - this.layoutSnapshot.bodyHeight\n );\n const widthDiff = Math.abs(\n document.body.scrollWidth - this.layoutSnapshot.bodyWidth\n );\n\n if (heightDiff > THRESHOLD || widthDiff > THRESHOLD) {\n return true;\n }\n\n // Check anchor element positions\n for (const [key, oldRect] of this.layoutSnapshot.anchors) {\n const el = this.findElementByKey(key);\n if (!el) {\n continue;\n }\n\n const newRect = el.getBoundingClientRect();\n const topDiff = Math.abs(newRect.top - oldRect.top);\n const leftDiff = Math.abs(newRect.left - oldRect.left);\n\n if (topDiff > THRESHOLD || leftDiff > THRESHOLD) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Find an element by its scrollable key\n *\n * @param {string} key - Element key\n * @returns {HTMLElement|null} Found element or null\n */\n findElementByKey(key) {\n if (key === 'window') {\n return null;\n }\n\n if (key.startsWith('#')) {\n return document.getElementById(key.slice(1));\n }\n\n try {\n return document.querySelector(key);\n } catch {\n return null;\n }\n }\n\n /**\n * Restore scroll positions if layout hasn't changed significantly\n *\n * Logs a message if scroll restoration is skipped due to layout changes.\n */\n restoreScrollPositions() {\n if (this.layoutChangedSignificantly()) {\n console.log('[HMR] Layout changed significantly, not restoring scroll');\n return;\n }\n\n // Restore window scroll\n const windowPos = this.scrollPositions.get('window');\n if (windowPos) {\n window.scrollTo(windowPos.left, windowPos.top);\n }\n\n // Restore container scrolls\n for (const [key, pos] of this.scrollPositions) {\n if (key === 'window') {\n continue;\n }\n\n const el = this.findElementByKey(key);\n if (el) {\n el.scrollTop = pos.top;\n el.scrollLeft = pos.left;\n }\n }\n }\n\n /**\n * Capture all state (form + scroll + layout)\n *\n * Convenience method that calls all capture methods.\n */\n captureAll() {\n this.captureFormState();\n this.captureScrollPositions();\n this.captureLayout();\n }\n\n /**\n * Restore all state (form + scroll)\n *\n * Convenience method that calls all restore methods.\n */\n restoreAll() {\n this.restoreFormState();\n this.restoreScrollPositions();\n }\n\n /**\n * Clear all captured state\n */\n clear() {\n this.capturedInputs.clear();\n this.scrollPositions.clear();\n this.layoutSnapshot = null;\n }\n}\n\n/**\n * Singleton state capturer instance\n * @type {StateCapturer}\n */\nexport const stateCapturer = new StateCapturer();\n", "/**\n * HMR Error Overlay\n *\n * Displays error information in a full-screen overlay with Shadow DOM isolation.\n * Provides click-to-open editor support and keyboard/click dismissal.\n *\n * @module @coherent.js/client/hmr/overlay\n */\n\n/**\n * Escape HTML special characters to prevent XSS.\n * @param {string} str - String to escape\n * @returns {string} Escaped string\n */\nexport function escapeHtml(str) {\n return String(str)\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n\n/**\n * Format code frame with line numbers and highlight.\n * @param {string} frame - Code frame content\n * @param {number} highlightLine - Line number to highlight (1-based)\n * @param {number} [startLine=1] - Starting line number for the frame\n * @returns {string} Formatted HTML string\n */\n/**\n * Coerce a line or column to a positive integer, or null when it is not one.\n *\n * These values reach the overlay markup without escaping \u2014 `line` even lands\n * inside a quoted attribute \u2014 so they are narrowed to integers here rather\n * than trusting the shape of whatever built the error object.\n *\n * @param {*} value - Candidate line or column\n * @returns {number|null} A positive integer, or null\n */\nfunction toPositiveInt(value) {\n const parsed = Number(value);\n return Number.isInteger(parsed) && parsed > 0 ? parsed : null;\n}\n\nexport function formatCodeFrame(frame, highlightLine, startLine = 1) {\n if (!frame) return '';\n\n // Interpolated into markup below without escaping, so a non-numeric\n // startLine would be concatenated rather than added.\n const firstLine = toPositiveInt(startLine) ?? 1;\n\n const lines = frame.split('\\n');\n return lines.map((content, i) => {\n const lineNum = firstLine + i;\n const isHighlight = lineNum === highlightLine;\n return `<div class=\"line${isHighlight ? ' highlight' : ''}\">\n <span class=\"line-number\">${lineNum}</span>\n <span class=\"line-content\">${escapeHtml(content)}</span>\n </div>`;\n }).join('');\n}\n\n/**\n * Editor URL scheme map for click-to-open functionality.\n */\nconst EDITOR_URLS = {\n vscode: (file, line) => `vscode://file/${file}:${line}`,\n cursor: (file, line) => `cursor://file/${file}:${line}`,\n 'vscode-insiders': (file, line) => `vscode-insiders://file/${file}:${line}`,\n atom: (file, line) => `atom://core/open/file?filename=${file}&line=${line}`,\n sublime: (file, line) => `subl://open?url=file://${file}&line=${line}`,\n webstorm: (file, line) => `webstorm://open?file=${file}&line=${line}`,\n idea: (file, line) => `idea://open?file=${file}&line=${line}`\n};\n\n/**\n * CSS styles for the error overlay (Dracula-inspired color scheme).\n */\nconst OVERLAY_STYLES = `\n :host {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n z-index: 99999;\n --bg: #181818;\n --text: #f8f8f2;\n --red: #ff5555;\n --yellow: #f1fa8c;\n --purple: #bd93f9;\n --cyan: #8be9fd;\n --code-bg: #282a36;\n --line-num: #6272a4;\n }\n .backdrop {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.66);\n }\n .container {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: min(800px, 90vw);\n max-height: 90vh;\n overflow: auto;\n background: var(--bg);\n border-radius: 8px;\n box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);\n font-family: 'SF Mono', Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;\n }\n .header {\n padding: 16px 20px;\n background: var(--red);\n color: white;\n display: flex;\n justify-content: space-between;\n align-items: center;\n border-radius: 8px 8px 0 0;\n }\n .title {\n font-weight: bold;\n font-size: 16px;\n }\n .close-btn {\n background: none;\n border: none;\n color: white;\n font-size: 24px;\n cursor: pointer;\n padding: 0 8px;\n line-height: 1;\n }\n .close-btn:hover {\n opacity: 0.8;\n }\n .content {\n padding: 20px;\n color: var(--text);\n }\n .message {\n font-size: 18px;\n color: var(--red);\n margin-bottom: 20px;\n word-break: break-word;\n }\n .file {\n color: var(--cyan);\n margin-bottom: 16px;\n cursor: pointer;\n text-decoration: underline;\n }\n .file:hover {\n color: var(--purple);\n }\n .code-frame {\n background: var(--code-bg);\n padding: 16px;\n border-radius: 4px;\n overflow-x: auto;\n font-size: 14px;\n line-height: 1.5;\n margin-bottom: 16px;\n }\n .line {\n display: flex;\n }\n .line-number {\n width: 50px;\n color: var(--line-num);\n text-align: right;\n padding-right: 16px;\n user-select: none;\n flex-shrink: 0;\n }\n .line-content {\n flex: 1;\n white-space: pre;\n }\n .line.highlight {\n background: rgba(255, 85, 85, 0.2);\n }\n .line.highlight .line-content {\n color: var(--red);\n }\n .stack {\n margin-top: 20px;\n font-size: 12px;\n color: var(--line-num);\n white-space: pre-wrap;\n max-height: 200px;\n overflow-y: auto;\n }\n .tip {\n margin-top: 16px;\n padding: 12px;\n background: rgba(189, 147, 249, 0.1);\n border-left: 3px solid var(--purple);\n font-size: 13px;\n color: var(--text);\n }\n .tip strong {\n color: var(--purple);\n }\n`;\n\n/**\n * Error overlay class for HMR error display.\n * Uses Shadow DOM for complete style isolation.\n */\nexport class ErrorOverlay {\n constructor() {\n /** @type {{ host: HTMLElement, shadow: ShadowRoot } | null} */\n this.overlay = null;\n /** @type {string} */\n this.editor = this._getStoredEditor();\n /** @type {((e: KeyboardEvent) => void) | null} */\n this.escapeHandler = null;\n }\n\n /**\n * Get stored editor preference from localStorage.\n * @returns {string} Editor name\n * @private\n */\n _getStoredEditor() {\n try {\n return localStorage.getItem('coherent-editor') || 'vscode';\n } catch {\n return 'vscode';\n }\n }\n\n /**\n * Create the overlay element with Shadow DOM.\n * @returns {{ host: HTMLElement, shadow: ShadowRoot }} Overlay elements\n */\n createOverlay() {\n if (this.overlay) return this.overlay;\n\n const host = document.createElement('div');\n host.id = 'coherent-error-overlay';\n const shadow = host.attachShadow({ mode: 'open' });\n\n const style = document.createElement('style');\n style.textContent = OVERLAY_STYLES;\n shadow.appendChild(style);\n\n this.overlay = { host, shadow };\n return this.overlay;\n }\n\n /**\n * Show the error overlay with error details.\n * @param {Object} error - Error details\n * @param {string} error.message - Error message\n * @param {string} [error.file] - File path\n * @param {number} [error.line] - Line number\n * @param {number} [error.column] - Column number\n * @param {string} [error.frame] - Code frame with context\n * @param {string} [error.stack] - Stack trace\n */\n show(error) {\n const { host, shadow } = this.createOverlay();\n\n // Clear previous content (but keep styles)\n const existingWrapper = shadow.querySelector('.wrapper');\n if (existingWrapper) existingWrapper.remove();\n\n const line = toPositiveInt(error.line);\n const column = toPositiveInt(error.column);\n\n // Calculate start line for code frame (center on error line)\n const frameLines = error.frame ? error.frame.split('\\n').length : 0;\n const startLine = line ? Math.max(1, line - Math.floor(frameLines / 2)) : 1;\n\n const wrapper = document.createElement('div');\n wrapper.className = 'wrapper';\n wrapper.innerHTML = `\n <div class=\"backdrop\"></div>\n <div class=\"container\">\n <div class=\"header\">\n <span class=\"title\">HMR Error</span>\n <button class=\"close-btn\" title=\"Close (Escape)\">×</button>\n </div>\n <div class=\"content\">\n <div class=\"message\">${escapeHtml(error.message || 'Unknown error')}</div>\n ${error.file ? `\n <div class=\"file\" data-file=\"${escapeHtml(error.file)}\" data-line=\"${line || 1}\">\n ${escapeHtml(error.file)}${line ? `:${line}` : ''}${column ? `:${column}` : ''}\n </div>\n ` : ''}\n ${error.frame ? `\n <div class=\"code-frame\">${formatCodeFrame(error.frame, line, startLine)}</div>\n ` : ''}\n ${error.stack ? `\n <div class=\"stack\">${escapeHtml(error.stack)}</div>\n ` : ''}\n <div class=\"tip\">\n Press <strong>Escape</strong> or click the X to dismiss.\n ${error.file ? ` Click the file path to open in ${escapeHtml(this.editor)}.` : ''}\n </div>\n </div>\n </div>\n `;\n\n shadow.appendChild(wrapper);\n\n // Event handlers\n const closeBtn = wrapper.querySelector('.close-btn');\n const backdrop = wrapper.querySelector('.backdrop');\n const fileLink = wrapper.querySelector('.file');\n\n closeBtn?.addEventListener('click', () => this.hide());\n backdrop?.addEventListener('click', () => this.hide());\n fileLink?.addEventListener('click', (e) => {\n const target = e.target;\n const file = target.dataset.file;\n const line = parseInt(target.dataset.line, 10) || 1;\n this.openInEditor(file, line);\n });\n\n // Escape key handler: one per visible overlay, however often show() runs\n if (!this.escapeHandler) {\n this.escapeHandler = (e) => {\n if (e.key === 'Escape') this.hide();\n };\n document.addEventListener('keydown', this.escapeHandler);\n }\n\n // Add to DOM if not already\n if (!host.parentNode) {\n document.body.appendChild(host);\n }\n }\n\n /**\n * Hide and remove the error overlay.\n */\n hide() {\n if (this.overlay?.host.parentNode) {\n this.overlay.host.parentNode.removeChild(this.overlay.host);\n }\n if (this.escapeHandler) {\n document.removeEventListener('keydown', this.escapeHandler);\n this.escapeHandler = null;\n }\n // Reset overlay reference so it can be recreated\n this.overlay = null;\n }\n\n /**\n * Open file in configured editor.\n * @param {string} file - File path\n * @param {number} [line=1] - Line number\n */\n openInEditor(file, line = 1) {\n const urlGenerator = EDITOR_URLS[this.editor] || EDITOR_URLS.vscode;\n const url = urlGenerator(file, line);\n window.open(url, '_self');\n }\n\n /**\n * Set preferred editor and store in localStorage.\n * @param {string} editor - Editor name (vscode, cursor, vscode-insiders, atom, sublime, webstorm, idea)\n */\n setEditor(editor) {\n this.editor = editor;\n try {\n localStorage.setItem('coherent-editor', editor);\n } catch {\n // Ignore localStorage errors\n }\n }\n}\n\n/**\n * Singleton instance of ErrorOverlay.\n */\nexport const errorOverlay = new ErrorOverlay();\n", "/**\n * HMR Connection Status Indicator\n *\n * Displays a small colored dot in the bottom-right corner indicating\n * WebSocket connection status. Unobtrusive design per CONTEXT.md decision.\n *\n * @module @coherent.js/client/hmr/indicator\n */\n\n/**\n * Status color mapping for connection states.\n */\nconst STATUS_COLORS = {\n connected: '#10b981', // Green\n disconnected: '#ef4444', // Red\n reconnecting: '#f59e0b', // Yellow/amber\n error: '#ef4444' // Red\n};\n\n/**\n * Status title mapping for accessibility.\n */\nconst STATUS_TITLES = {\n connected: 'HMR: Connected',\n disconnected: 'HMR: Disconnected',\n reconnecting: 'HMR: Reconnecting...',\n error: 'HMR: Error'\n};\n\n/**\n * Default/initial color before status is set.\n */\nconst DEFAULT_COLOR = '#666';\n\n/**\n * Connection status indicator class.\n * Shows an 8px colored dot in the bottom-right corner of the viewport.\n */\nexport class ConnectionIndicator {\n constructor() {\n /** @type {HTMLElement | null} */\n this.indicator = null;\n }\n\n /**\n * Create the indicator element if it doesn't exist.\n * Uses inline styles to avoid external CSS dependencies.\n */\n create() {\n if (this.indicator) return;\n\n const el = document.createElement('div');\n el.id = 'coherent-hmr-indicator';\n el.style.cssText = `\n position: fixed;\n bottom: 8px;\n right: 8px;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: ${DEFAULT_COLOR};\n z-index: 99998;\n pointer-events: none;\n transition: background 0.3s ease;\n `;\n el.title = 'HMR: Initializing';\n\n document.body.appendChild(el);\n this.indicator = el;\n }\n\n /**\n * Update the indicator status.\n * Creates the element if it doesn't exist (lazy initialization).\n *\n * @param {string} status - Status string: 'connected', 'disconnected', 'reconnecting', or 'error'\n */\n update(status) {\n if (!this.indicator) {\n this.create();\n }\n\n const color = STATUS_COLORS[status] || STATUS_COLORS.disconnected;\n const title = STATUS_TITLES[status] || 'HMR: Unknown';\n\n this.indicator.style.background = color;\n this.indicator.title = title;\n }\n\n /**\n * Remove the indicator from the DOM.\n */\n destroy() {\n if (this.indicator?.parentNode) {\n this.indicator.parentNode.removeChild(this.indicator);\n }\n this.indicator = null;\n }\n}\n\n/**\n * Singleton instance of ConnectionIndicator.\n */\nexport const connectionIndicator = new ConnectionIndicator();\n", "/**\n * ModuleTracker - Tracks module graph and HMR boundaries\n *\n * Provides a Vite-compatible hot context API for modules to declare\n * how they should be handled during HMR updates. Tracks accept/dispose\n * handlers and persistent data that survives updates.\n *\n * @module @coherent.js/client/hmr/module-tracker\n */\n\n/**\n * Tracks module HMR registrations and provides hot context API\n */\nexport class ModuleTracker {\n constructor() {\n /**\n * Module registrations map\n * @type {Map<string, { accept: Function|null, acceptDeps: { deps: string[], callback: Function }|null, dispose: Function|null, prune: Function|null, data: Object }>}\n */\n this.modules = new Map();\n\n /**\n * WebSocket reference for invalidation messages\n * @type {WebSocket|null}\n */\n this.socket = null;\n }\n\n /**\n * Set WebSocket reference for invalidation messages\n * @param {WebSocket|null} socket - WebSocket connection\n */\n setSocket(socket) {\n this.socket = socket;\n }\n\n /**\n * Create a hot context for a module (Vite-compatible API)\n *\n * Returns an object with:\n * - data: Persistent object that survives HMR updates\n * - accept(callback): Register self-update handler\n * - acceptDeps(deps, callback): Register dependency update handler\n * - dispose(callback): Register cleanup handler called before replacement\n * - prune(callback): Register handler for when module is removed\n * - invalidate(message): Signal that module cannot hot-update\n *\n * @param {string} moduleId - Unique identifier for the module\n * @returns {Object} Hot context object\n */\n createHotContext(moduleId) {\n // Get or create module data, preserving existing data object\n let moduleData = this.modules.get(moduleId);\n if (!moduleData) {\n moduleData = {\n accept: null,\n acceptDeps: null,\n dispose: null,\n prune: null,\n data: {},\n };\n this.modules.set(moduleId, moduleData);\n }\n\n const tracker = this;\n\n return {\n /**\n * Persistent data object that survives HMR updates.\n * Use this to preserve state across module replacements.\n */\n get data() {\n return moduleData.data;\n },\n\n /**\n * Accept self updates.\n * Called when this module is updated and can handle its own replacement.\n *\n * @param {Function} [callback] - Optional callback receiving the new module\n */\n accept(callback) {\n moduleData.accept = callback || (() => {});\n },\n\n /**\n * Accept dependency updates.\n * Called when one of the specified dependencies is updated.\n *\n * @param {string|string[]} deps - Dependency module ID(s)\n * @param {Function} callback - Callback receiving updated dependencies\n */\n acceptDeps(deps, callback) {\n const depsArray = Array.isArray(deps) ? deps : [deps];\n moduleData.acceptDeps = { deps: depsArray, callback };\n },\n\n /**\n * Register disposal callback.\n * Called before the module is replaced, receives the data object\n * to allow saving state for the next version.\n *\n * @param {Function} callback - Cleanup handler, receives data object\n */\n dispose(callback) {\n moduleData.dispose = callback;\n },\n\n /**\n * Register prune callback.\n * Called when the module is completely removed from the module graph.\n *\n * @param {Function} callback - Prune handler\n */\n prune(callback) {\n moduleData.prune = callback;\n },\n\n /**\n * Invalidate this module.\n * Signals that the module cannot be hot-updated and should propagate\n * the update to its importers.\n *\n * @param {string} [message] - Optional message explaining why\n */\n invalidate(message) {\n const WS_OPEN = typeof WebSocket !== 'undefined' ? WebSocket.OPEN : 1;\n if (tracker.socket?.readyState === WS_OPEN) {\n tracker.socket.send(JSON.stringify({\n type: 'invalidate',\n moduleId,\n message,\n }));\n }\n // Log for debugging\n console.log(`[HMR] Module ${moduleId} invalidated${message ? `: ${message}` : ''}`);\n },\n };\n }\n\n /**\n * Check if a module can be hot-updated\n *\n * Returns true if the module has registered an accept handler.\n *\n * @param {string} moduleId - Module identifier\n * @returns {boolean} True if module accepts HMR updates\n */\n canHotUpdate(moduleId) {\n const moduleData = this.modules.get(moduleId);\n return !!(moduleData?.accept || moduleData?.acceptDeps);\n }\n\n /**\n * Check if a module is an HMR boundary\n *\n * A module is considered a boundary if:\n * - It has an accept handler registered\n * - It exports __hmrBoundary = true\n * - It is associated with a data-coherent-component element\n *\n * @param {string} moduleId - Module identifier\n * @param {Object} [moduleExports] - Optional module exports to check for __hmrBoundary\n * @returns {boolean} True if module is an HMR boundary\n */\n isHmrBoundary(moduleId, moduleExports) {\n // Check for accept handler\n if (this.canHotUpdate(moduleId)) {\n return true;\n }\n\n // Check for explicit __hmrBoundary export\n if (moduleExports?.__hmrBoundary === true) {\n return true;\n }\n\n // Check for coherent component presence (by convention)\n // Extract component name from module path\n const componentName = this.extractComponentName(moduleId);\n if (componentName && typeof document !== 'undefined') {\n const hasComponent = document.querySelector(\n `[data-coherent-component=\"${componentName}\"]`\n );\n if (hasComponent) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Extract a potential component name from module path\n *\n * @param {string} moduleId - Module path\n * @returns {string|null} Component name or null\n * @private\n */\n extractComponentName(moduleId) {\n // Extract filename without extension\n const match = moduleId.match(/\\/([^/]+?)(?:\\.[^.]+)?$/);\n if (match) {\n return match[1];\n }\n return null;\n }\n\n /**\n * Execute dispose callback for a module\n *\n * Calls the registered dispose handler with the data object,\n * allowing the module to save state for the next version.\n *\n * @param {string} moduleId - Module identifier\n * @returns {Object|null} The data object (for passing to next version)\n */\n executeDispose(moduleId) {\n const moduleData = this.modules.get(moduleId);\n if (!moduleData) {\n return null;\n }\n\n // Call dispose handler with data object\n if (typeof moduleData.dispose === 'function') {\n try {\n moduleData.dispose(moduleData.data);\n } catch (err) {\n console.error(`[HMR] Error in dispose handler for ${moduleId}:`, err);\n }\n }\n\n return moduleData.data;\n }\n\n /**\n * Execute accept callback for a module\n *\n * Calls the registered accept handler with the new module.\n *\n * @param {string} moduleId - Module identifier\n * @param {Object} [newModule] - The newly imported module\n * @returns {boolean} True if accept handler was called\n */\n executeAccept(moduleId, newModule) {\n const moduleData = this.modules.get(moduleId);\n if (!moduleData?.accept) {\n return false;\n }\n\n try {\n moduleData.accept(newModule);\n return true;\n } catch (err) {\n console.error(`[HMR] Error in accept handler for ${moduleId}:`, err);\n return false;\n }\n }\n\n /**\n * Execute acceptDeps callback for a module\n *\n * Calls the registered acceptDeps handler with the updated dependencies.\n *\n * @param {string} moduleId - Module identifier\n * @param {Object} updatedDeps - Map of dependency moduleId -> new module\n * @returns {boolean} True if acceptDeps handler was called\n */\n executeAcceptDeps(moduleId, updatedDeps) {\n const moduleData = this.modules.get(moduleId);\n if (!moduleData?.acceptDeps) {\n return false;\n }\n\n try {\n const { deps, callback } = moduleData.acceptDeps;\n // Build array of updated modules in same order as deps\n const modules = deps.map((dep) => updatedDeps[dep]);\n callback(modules);\n return true;\n } catch (err) {\n console.error(`[HMR] Error in acceptDeps handler for ${moduleId}:`, err);\n return false;\n }\n }\n\n /**\n * Execute prune callback for a module\n *\n * Called when a module is removed from the module graph.\n *\n * @param {string} moduleId - Module identifier\n */\n executePrune(moduleId) {\n const moduleData = this.modules.get(moduleId);\n if (!moduleData?.prune) {\n return;\n }\n\n try {\n moduleData.prune();\n } catch (err) {\n console.error(`[HMR] Error in prune handler for ${moduleId}:`, err);\n }\n\n // Remove module from tracking\n this.modules.delete(moduleId);\n }\n\n /**\n * Check if module is registered\n *\n * @param {string} moduleId - Module identifier\n * @returns {boolean} True if module is registered\n */\n hasModule(moduleId) {\n return this.modules.has(moduleId);\n }\n\n /**\n * Get module data (for testing/debugging)\n *\n * @param {string} moduleId - Module identifier\n * @returns {Object|null} Module data or null\n */\n getModuleData(moduleId) {\n return this.modules.get(moduleId) || null;\n }\n\n /**\n * Clear all module registrations (for testing)\n */\n clear() {\n this.modules.clear();\n }\n}\n\n/**\n * Singleton module tracker instance\n * @type {ModuleTracker}\n */\nexport const moduleTracker = new ModuleTracker();\n\n/**\n * Convenience function to create a hot context for a module\n *\n * @param {string} moduleId - Module identifier\n * @returns {Object} Hot context object\n */\nexport function createHotContext(moduleId) {\n return moduleTracker.createHotContext(moduleId);\n}\n", "/**\n * HMRClient - Orchestrates WebSocket connection and HMR updates\n *\n * Integrates all HMR modules (cleanup tracker, state capturer, error overlay,\n * connection indicator) to provide seamless hot module replacement with\n * state preservation.\n *\n * @module @coherent.js/client/hmr/client\n */\n\nimport { cleanupTracker } from './cleanup-tracker.js';\nimport { stateCapturer } from './state-capturer.js';\nimport { errorOverlay } from './overlay.js';\nimport { connectionIndicator } from './indicator.js';\nimport { moduleTracker } from './module-tracker.js';\n\n/** Longest stack line worth parsing; real frames are far shorter. */\nconst MAX_STACK_LINE_LENGTH = 1024;\n\n/** Most frames worth parsing; V8's default Error.stackTraceLimit is 10. */\nconst MAX_STACK_LINES = 50;\n\n/**\n * Parse error stack to extract file/line info\n *\n * @param {Error} error - Error object\n * @returns {{ file: string|null, line: number|null, column: number|null }} Parsed location\n */\nfunction parseErrorLocation(error) {\n const result = { file: null, line: null, column: null };\n\n if (!error.stack) {\n return result;\n }\n\n // Match common stack trace formats:\n // Chrome/Node: \"at Function (file.js:10:5)\"\n // Firefox: \"Function@file.js:10:5\"\n // Safari: \"file.js:10:5\"\n const patterns = [\n /at\\s[^(]*\\(([^()]+):(\\d+):(\\d+)\\)/, // Chrome/Node with parens\n /at\\s+([^\\s].*):(\\d+):(\\d+)/, // Chrome/Node without parens\n /@([^@]+):(\\d+):(\\d+)/, // Firefox\n /^(.+?):(\\d+):(\\d+)/, // Safari\n ];\n\n // These patterns scan from every \"at\" or \"@\" in a line, so an oversized\n // line costs O(n^2): one 100,000-character line took 4.7s. Real frames are\n // short, and V8 caps a stack at 10 frames by default, so bounding both\n // makes the worst case constant without affecting any genuine stack.\n // CodeQL js/polynomial-redos.\n const lines = error.stack.split('\\n', MAX_STACK_LINES);\n for (const line of lines) {\n if (line.length > MAX_STACK_LINE_LENGTH) continue;\n for (const pattern of patterns) {\n const match = line.match(pattern);\n if (match) {\n result.file = match[1];\n result.line = parseInt(match[2], 10);\n result.column = parseInt(match[3], 10);\n return result;\n }\n }\n }\n\n return result;\n}\n\n/**\n * HMR Client class - manages WebSocket connection and update orchestration\n */\nexport class HMRClient {\n constructor() {\n /**\n * WebSocket connection\n * @type {WebSocket|null}\n */\n this.socket = null;\n\n /**\n * Connection state\n * @type {boolean}\n */\n this.connected = false;\n\n /**\n * Reconnection attempt counter\n * @type {number}\n */\n this.reconnectAttempts = 0;\n\n /**\n * Maximum reconnection attempts\n * @type {number}\n */\n this.maxReconnectAttempts = 10;\n\n /**\n * Base reconnection delay in ms\n * @type {number}\n */\n this.reconnectDelay = 1000;\n\n /**\n * Whether we've had a disconnect (for reload detection)\n * @type {boolean}\n */\n this.hadDisconnect = false;\n\n /**\n * Reconnect timeout ID\n * @type {number|null}\n */\n this.reconnectTimeout = null;\n\n /**\n * Initialization flag\n * @type {boolean}\n */\n this.initialized = false;\n }\n\n /**\n * Connect to the dev server WebSocket\n *\n * Establishes WebSocket connection with automatic reconnection using\n * exponential backoff with jitter.\n *\n * @returns {void}\n */\n connect() {\n if (typeof window === 'undefined') {\n return;\n }\n\n // Clear any pending reconnect\n if (this.reconnectTimeout !== null) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = null;\n }\n\n try {\n const protocol = location.protocol === 'https:' ? 'wss' : 'ws';\n const wsUrl = `${protocol}://${location.host}`;\n const socket = new WebSocket(wsUrl);\n this.socket = socket;\n\n // Share socket with module tracker for invalidation messages\n moduleTracker.setSocket(socket);\n\n // Events from a socket that disconnect() dropped, or that a newer\n // connection replaced, are ignored: its close must not reconnect.\n const isCurrent = () => this.socket === socket;\n\n socket.addEventListener('open', () => {\n if (!isCurrent()) return;\n console.log('[HMR] Connected');\n this.connected = true;\n this.reconnectAttempts = 0;\n connectionIndicator.update('connected');\n\n // Send connection acknowledgment\n socket.send(JSON.stringify({ type: 'connected' }));\n\n // If reconnecting after disconnect, reload (server may have restarted)\n if (this.hadDisconnect) {\n console.log('[HMR] Reconnected after disconnect, reloading page');\n setTimeout(() => this.reload(), 200);\n return;\n }\n });\n\n socket.addEventListener('close', () => {\n if (!isCurrent()) return;\n this.connected = false;\n this.hadDisconnect = true;\n connectionIndicator.update('disconnected');\n moduleTracker.setSocket(null);\n this.scheduleReconnect();\n });\n\n socket.addEventListener('error', (event) => {\n if (!isCurrent()) return;\n console.warn('[HMR] WebSocket error:', event);\n connectionIndicator.update('error');\n try {\n socket.close();\n } catch {\n // Ignore close errors\n }\n });\n\n socket.addEventListener('message', (event) => {\n if (!isCurrent()) return;\n this.handleMessage(event);\n });\n } catch (error) {\n console.warn('[HMR] Failed to connect:', error);\n this.scheduleReconnect();\n }\n }\n\n /**\n * Schedule a reconnection attempt with exponential backoff\n *\n * @private\n */\n scheduleReconnect() {\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n console.warn('[HMR] Max reconnection attempts reached');\n connectionIndicator.update('disconnected');\n return;\n }\n\n connectionIndicator.update('reconnecting');\n\n // Exponential backoff with jitter\n const delay = Math.min(\n this.reconnectDelay * Math.pow(2, this.reconnectAttempts) + Math.random() * 1000,\n 30000\n );\n this.reconnectAttempts++;\n\n console.log(`[HMR] Reconnecting in ${Math.round(delay)}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);\n\n this.reconnectTimeout = setTimeout(() => {\n this.reconnectTimeout = null;\n this.connect();\n }, delay);\n }\n\n /**\n * Handle incoming WebSocket message\n *\n * @param {MessageEvent} event - WebSocket message event\n * @private\n */\n handleMessage(event) {\n let data;\n try {\n data = JSON.parse(event.data);\n } catch {\n return;\n }\n\n console.log('[HMR] message', data.type, data.filePath || data.webPath || '');\n\n switch (data.type) {\n case 'connected':\n // Connection acknowledged\n break;\n\n case 'hmr-full-reload':\n case 'reload':\n console.warn('[HMR] Server requested full reload');\n this.reload();\n break;\n\n case 'hmr-component-update':\n case 'hmr-update':\n this.handleUpdate(data);\n break;\n\n case 'hmr-error':\n this.showError(data.error || data);\n break;\n\n case 'preview-update':\n // No-op: used by dashboard for live preview panes\n break;\n\n default:\n // Unknown message type, ignore\n break;\n }\n }\n\n /**\n * Handle module update\n *\n * Orchestrates the full HMR update cycle:\n * 1. Capture form/scroll state\n * 2. Execute dispose handlers\n * 3. Clean up module resources\n * 4. Re-import module\n * 5. Execute accept handlers, or reload the page when the module does not\n * accept updates (nothing else can apply its new code)\n * 6. Restore state\n *\n * @param {Object} data - Update message data\n * @param {string} [data.filePath] - File path that changed\n * @param {string} [data.webPath] - Web-accessible path\n * @param {string} [data.updateType] - Type of update (component, style, etc.)\n */\n async handleUpdate(data) {\n const filePath = data.webPath || data.filePath || '';\n const moduleId = filePath;\n\n try {\n // 1. Capture current state\n stateCapturer.captureAll();\n\n // 2. Execute dispose handler if registered\n if (moduleTracker.hasModule(moduleId)) {\n moduleTracker.executeDispose(moduleId);\n }\n\n // 3. Clean up module resources\n if (cleanupTracker.hasResources(moduleId)) {\n cleanupTracker.checkForLeaks(moduleId);\n cleanupTracker.cleanup(moduleId);\n }\n\n // 4. Re-import module with cache bust\n const importPath = filePath.startsWith('/') ? filePath : `/${filePath}`;\n const newModule = await this.importModule(`${importPath}?t=${Date.now()}`);\n\n // 5. Execute accept handler if module can hot-update\n if (!moduleTracker.canHotUpdate(moduleId)) {\n // No accept handler: only a reload runs the new code\n console.log(`[HMR] ${filePath} does not accept hot updates, reloading`);\n this.reload();\n return;\n }\n moduleTracker.executeAccept(moduleId, newModule);\n\n // 6. Restore state\n stateCapturer.restoreAll();\n\n // 7. Hide error overlay (in case previous error is now fixed)\n errorOverlay.hide();\n\n // 8. Log success\n console.log(`[HMR] Updated: ${data.updateType || 'module'} ${filePath}`);\n } catch (error) {\n this.handleUpdateError(error, filePath);\n }\n }\n\n /**\n * Import an updated module (overridable, e.g. in tests)\n *\n * @param {string} url - Module URL with cache-busting query\n * @returns {Promise<Object>} Module namespace\n */\n importModule(url) {\n return import(/* @vite-ignore */ url);\n }\n\n /**\n * Reload the page\n */\n reload() {\n location.reload();\n }\n\n /**\n * Handle update error\n *\n * @param {Error} error - Error that occurred\n * @param {string} filePath - File that was being updated\n * @private\n */\n handleUpdateError(error, filePath) {\n console.error('[HMR] Update failed:', error);\n\n // Parse error location from stack\n const location = parseErrorLocation(error);\n\n // Build error details for overlay\n const errorDetails = {\n message: error.message || 'Unknown error during HMR update',\n file: location.file || filePath,\n line: location.line,\n column: location.column,\n stack: error.stack,\n };\n\n this.showError(errorDetails);\n }\n\n /**\n * Show error overlay\n *\n * @param {Object} error - Error details\n */\n showError(error) {\n errorOverlay.show(error);\n }\n\n /**\n * Hide error overlay\n */\n hideError() {\n errorOverlay.hide();\n }\n\n /**\n * Initialize HMR client\n *\n * Guards against double initialization and connects to dev server.\n *\n * @returns {void}\n */\n initialize() {\n if (typeof window === 'undefined') {\n return;\n }\n\n // Guard against double initialization\n if (window.__coherent_hmr_initialized || this.initialized) {\n return;\n }\n\n window.__coherent_hmr_initialized = true;\n this.initialized = true;\n\n this.connect();\n }\n\n /**\n * Disconnect and clean up\n *\n * @returns {void}\n */\n disconnect() {\n if (this.reconnectTimeout !== null) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = null;\n }\n\n if (this.socket) {\n const socket = this.socket;\n // Cleared first, so the close event of this socket is ignored rather\n // than scheduling a reconnect\n this.socket = null;\n try {\n socket.close();\n } catch {\n // Ignore close errors\n }\n }\n\n this.connected = false;\n moduleTracker.setSocket(null);\n connectionIndicator.destroy();\n }\n\n /**\n * Check if client is connected\n *\n * @returns {boolean} True if connected\n */\n isConnected() {\n return this.connected;\n }\n}\n\n/**\n * Singleton HMR client instance\n * @type {HMRClient}\n */\nexport const hmrClient = new HMRClient();\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["line", "location"]
|
|
7
|
+
}
|
|
@@ -74,26 +74,95 @@ var handlerRegistry = new HandlerRegistry();
|
|
|
74
74
|
|
|
75
75
|
// src/events/wrapper.js
|
|
76
76
|
function wrapEvent(originalEvent, target, componentRef = null) {
|
|
77
|
-
|
|
77
|
+
const wrapped = {
|
|
78
78
|
// Native event access
|
|
79
79
|
originalEvent,
|
|
80
|
+
type: originalEvent?.type,
|
|
80
81
|
target,
|
|
82
|
+
currentTarget: target,
|
|
83
|
+
propagationStopped: false,
|
|
84
|
+
get defaultPrevented() {
|
|
85
|
+
return Boolean(originalEvent?.defaultPrevented);
|
|
86
|
+
},
|
|
81
87
|
// Delegate common methods
|
|
82
88
|
preventDefault() {
|
|
83
89
|
originalEvent.preventDefault();
|
|
84
90
|
},
|
|
85
91
|
stopPropagation() {
|
|
92
|
+
wrapped.propagationStopped = true;
|
|
86
93
|
originalEvent.stopPropagation();
|
|
87
94
|
},
|
|
95
|
+
stopImmediatePropagation() {
|
|
96
|
+
wrapped.propagationStopped = true;
|
|
97
|
+
if (typeof originalEvent.stopImmediatePropagation === "function") {
|
|
98
|
+
originalEvent.stopImmediatePropagation();
|
|
99
|
+
} else {
|
|
100
|
+
originalEvent.stopPropagation();
|
|
101
|
+
}
|
|
102
|
+
},
|
|
88
103
|
// Component context (null if no componentRef provided)
|
|
89
104
|
component: componentRef?.component ?? null,
|
|
90
105
|
state: componentRef?.state ?? null,
|
|
91
106
|
setState: componentRef?.setState ?? null,
|
|
92
107
|
props: componentRef?.props ?? null
|
|
93
108
|
};
|
|
109
|
+
return wrapped;
|
|
94
110
|
}
|
|
95
111
|
|
|
96
112
|
// src/events/delegation.js
|
|
113
|
+
var DEFAULT_EVENT_TYPES = [
|
|
114
|
+
"click",
|
|
115
|
+
"change",
|
|
116
|
+
"input",
|
|
117
|
+
"submit",
|
|
118
|
+
"focus",
|
|
119
|
+
"blur",
|
|
120
|
+
"keydown",
|
|
121
|
+
"keyup",
|
|
122
|
+
"keypress"
|
|
123
|
+
];
|
|
124
|
+
var NON_BUBBLING_EVENTS = /* @__PURE__ */ new Set([
|
|
125
|
+
"mouseenter",
|
|
126
|
+
"mouseleave",
|
|
127
|
+
"pointerenter",
|
|
128
|
+
"pointerleave",
|
|
129
|
+
"load",
|
|
130
|
+
"error",
|
|
131
|
+
"abort",
|
|
132
|
+
"scroll",
|
|
133
|
+
"toggle",
|
|
134
|
+
"invalid",
|
|
135
|
+
"cancel",
|
|
136
|
+
"close",
|
|
137
|
+
"play",
|
|
138
|
+
"pause",
|
|
139
|
+
"ended",
|
|
140
|
+
"playing",
|
|
141
|
+
"waiting",
|
|
142
|
+
"seeked",
|
|
143
|
+
"seeking",
|
|
144
|
+
"canplay",
|
|
145
|
+
"canplaythrough",
|
|
146
|
+
"durationchange",
|
|
147
|
+
"emptied",
|
|
148
|
+
"loadeddata",
|
|
149
|
+
"loadedmetadata",
|
|
150
|
+
"loadstart",
|
|
151
|
+
"progress",
|
|
152
|
+
"ratechange",
|
|
153
|
+
"stalled",
|
|
154
|
+
"suspend",
|
|
155
|
+
"timeupdate",
|
|
156
|
+
"volumechange"
|
|
157
|
+
]);
|
|
158
|
+
var CAPTURED_BUBBLING_EVENTS = /* @__PURE__ */ new Set(["focus", "blur"]);
|
|
159
|
+
var PASSIVE_EVENTS = /* @__PURE__ */ new Set(["touchstart", "touchmove", "wheel", "mousewheel", "scroll"]);
|
|
160
|
+
function getListenerOptions(eventType) {
|
|
161
|
+
return {
|
|
162
|
+
capture: CAPTURED_BUBBLING_EVENTS.has(eventType) || NON_BUBBLING_EVENTS.has(eventType),
|
|
163
|
+
passive: PASSIVE_EVENTS.has(eventType)
|
|
164
|
+
};
|
|
165
|
+
}
|
|
97
166
|
var EventDelegation = class {
|
|
98
167
|
/**
|
|
99
168
|
* @param {import('./registry.js').HandlerRegistry} [registry] - Handler registry instance
|
|
@@ -103,17 +172,7 @@ var EventDelegation = class {
|
|
|
103
172
|
this.initialized = false;
|
|
104
173
|
this.root = null;
|
|
105
174
|
this.boundHandlers = /* @__PURE__ */ new Map();
|
|
106
|
-
this.eventTypes = [
|
|
107
|
-
"click",
|
|
108
|
-
"change",
|
|
109
|
-
"input",
|
|
110
|
-
"submit",
|
|
111
|
-
"focus",
|
|
112
|
-
"blur",
|
|
113
|
-
"keydown",
|
|
114
|
-
"keyup",
|
|
115
|
-
"keypress"
|
|
116
|
-
];
|
|
175
|
+
this.eventTypes = [...DEFAULT_EVENT_TYPES];
|
|
117
176
|
}
|
|
118
177
|
/**
|
|
119
178
|
* Initialize event delegation by attaching listeners to the root element
|
|
@@ -127,20 +186,43 @@ var EventDelegation = class {
|
|
|
127
186
|
return;
|
|
128
187
|
}
|
|
129
188
|
this.root = root;
|
|
189
|
+
this.initialized = true;
|
|
130
190
|
for (const eventType of this.eventTypes) {
|
|
131
|
-
|
|
132
|
-
const useCapture = eventType === "focus" || eventType === "blur";
|
|
133
|
-
const options = {
|
|
134
|
-
capture: useCapture,
|
|
135
|
-
passive: eventType !== "submit"
|
|
136
|
-
};
|
|
137
|
-
root.addEventListener(eventType, handler, options);
|
|
138
|
-
this.boundHandlers.set(eventType, { handler, options });
|
|
191
|
+
this.attach(eventType);
|
|
139
192
|
}
|
|
140
|
-
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Make sure events of `eventType` are delegated. Called by hydrate() for
|
|
196
|
+
* every event type a component handles, so types beyond the defaults
|
|
197
|
+
* (dblclick, mouseenter, pointerdown, ...) work too.
|
|
198
|
+
* @param {string} eventType - DOM event type, e.g. 'dblclick'
|
|
199
|
+
*/
|
|
200
|
+
listen(eventType) {
|
|
201
|
+
if (!this.eventTypes.includes(eventType)) {
|
|
202
|
+
this.eventTypes.push(eventType);
|
|
203
|
+
}
|
|
204
|
+
if (this.initialized) {
|
|
205
|
+
this.attach(eventType);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/** @private */
|
|
209
|
+
attach(eventType) {
|
|
210
|
+
if (this.boundHandlers.has(eventType)) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const handler = (event) => this.handleEvent(event, eventType);
|
|
214
|
+
const options = getListenerOptions(eventType);
|
|
215
|
+
this.root.addEventListener(eventType, handler, options);
|
|
216
|
+
this.boundHandlers.set(eventType, { handler, options });
|
|
141
217
|
}
|
|
142
218
|
/**
|
|
143
219
|
* Handle a delegated event
|
|
220
|
+
*
|
|
221
|
+
* Runs the handler of the nearest element carrying
|
|
222
|
+
* `data-coherent-{eventType}`, then those of its ancestors, like native
|
|
223
|
+
* bubbling, until a handler stops propagation. Non-bubbling events only run
|
|
224
|
+
* a handler on the target itself.
|
|
225
|
+
*
|
|
144
226
|
* @param {Event} event - The DOM event
|
|
145
227
|
* @param {string} eventType - The type of event (click, change, etc.)
|
|
146
228
|
*/
|
|
@@ -150,20 +232,39 @@ var EventDelegation = class {
|
|
|
150
232
|
return;
|
|
151
233
|
}
|
|
152
234
|
const attrName = `data-coherent-${eventType}`;
|
|
153
|
-
const
|
|
154
|
-
|
|
235
|
+
const selector = `[${attrName}]`;
|
|
236
|
+
const bubbles = !NON_BUBBLING_EVENTS.has(eventType);
|
|
237
|
+
let current = target.closest(selector);
|
|
238
|
+
if (!bubbles && current !== target) {
|
|
155
239
|
return;
|
|
156
240
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
241
|
+
while (current) {
|
|
242
|
+
const handlerId = current.getAttribute(attrName);
|
|
243
|
+
const entry = handlerId ? this.registry.get(handlerId) : void 0;
|
|
244
|
+
if (entry) {
|
|
245
|
+
const wrappedEvent = wrapEvent(event, current, entry.componentRef);
|
|
246
|
+
entry.handler(wrappedEvent);
|
|
247
|
+
if (wrappedEvent.propagationStopped || event.cancelBubble === true) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (!bubbles) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const parent = current.parentElement;
|
|
255
|
+
if (!parent || typeof parent.closest !== "function" || !this.isWithinRoot(parent)) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
current = parent.closest(selector);
|
|
160
259
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
260
|
+
}
|
|
261
|
+
/** @private */
|
|
262
|
+
isWithinRoot(element) {
|
|
263
|
+
const root = this.root;
|
|
264
|
+
if (!root || root.nodeType === 9 || typeof root.contains !== "function") {
|
|
265
|
+
return true;
|
|
164
266
|
}
|
|
165
|
-
|
|
166
|
-
entry.handler(wrappedEvent);
|
|
267
|
+
return root.contains(element);
|
|
167
268
|
}
|
|
168
269
|
/**
|
|
169
270
|
* Destroy the event delegation system
|
|
@@ -178,6 +279,7 @@ var EventDelegation = class {
|
|
|
178
279
|
}
|
|
179
280
|
this.boundHandlers.clear();
|
|
180
281
|
this.registry.clear();
|
|
282
|
+
this.eventTypes = [...DEFAULT_EVENT_TYPES];
|
|
181
283
|
this.initialized = false;
|
|
182
284
|
this.root = null;
|
|
183
285
|
}
|
|
@@ -198,4 +300,4 @@ export {
|
|
|
198
300
|
EventDelegation,
|
|
199
301
|
eventDelegation
|
|
200
302
|
};
|
|
201
|
-
//# sourceMappingURL=chunk-
|
|
303
|
+
//# sourceMappingURL=chunk-UYL3RRRC.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/events/registry.js", "../src/events/wrapper.js", "../src/events/delegation.js"],
|
|
4
|
+
"sourcesContent": ["/**\n * Handler Registry for Coherent.js Event Delegation\n *\n * Maps handler IDs to their corresponding functions and component context.\n * Handlers are identified by ID (from data-coherent-{event} attributes) rather\n * than by element reference, allowing them to survive DOM updates.\n */\n\n/**\n * HandlerRegistry class\n * Stores handler functions with their associated component context\n */\nexport class HandlerRegistry {\n constructor() {\n /** @type {Map<string, {handler: Function, componentRef: object|null}>} */\n this.handlers = new Map();\n }\n\n /**\n * Register a handler with optional component context\n * @param {string} handlerId - Unique identifier for the handler\n * @param {Function} handler - The event handler function\n * @param {object|null} componentRef - Optional component reference with state/setState\n */\n register(handlerId, handler, componentRef = null) {\n if (typeof handler !== 'function') {\n throw new Error(`Handler must be a function, received: ${typeof handler}`);\n }\n this.handlers.set(handlerId, { handler, componentRef });\n }\n\n /**\n * Unregister a handler by ID\n * @param {string} handlerId - The handler ID to remove\n * @returns {boolean} True if handler was removed, false if not found\n */\n unregister(handlerId) {\n return this.handlers.delete(handlerId);\n }\n\n /**\n * Get a handler entry by ID\n * @param {string} handlerId - The handler ID to look up\n * @returns {{handler: Function, componentRef: object|null}|undefined} Handler entry or undefined\n */\n get(handlerId) {\n return this.handlers.get(handlerId);\n }\n\n /**\n * Check if a handler is registered\n * @param {string} handlerId - The handler ID to check\n * @returns {boolean} True if handler exists\n */\n has(handlerId) {\n return this.handlers.has(handlerId);\n }\n\n /**\n * Clear all registered handlers\n */\n clear() {\n this.handlers.clear();\n }\n\n /**\n * Get all handler IDs registered for a specific component\n * @param {object} componentRef - The component reference to search for\n * @returns {string[]} Array of handler IDs belonging to this component\n */\n getByComponent(componentRef) {\n if (!componentRef) {\n return [];\n }\n\n const handlerIds = [];\n for (const [handlerId, entry] of this.handlers) {\n if (entry.componentRef === componentRef) {\n handlerIds.push(handlerId);\n }\n }\n return handlerIds;\n }\n\n /**\n * Get the number of registered handlers\n * @returns {number} Count of registered handlers\n */\n get size() {\n return this.handlers.size;\n }\n}\n\n/**\n * Singleton handler registry instance\n * Use this for global event delegation\n */\nexport const handlerRegistry = new HandlerRegistry();\n", "/**\n * Event Wrapper for Coherent.js\n *\n * Wraps native DOM events with component context, providing handlers\n * access to component state, setState, and props.\n */\n\n/**\n * @typedef {object} CoherentEvent\n * @property {Event} originalEvent - The native DOM event\n * @property {string} type - The event type\n * @property {Element} target - The element with the data-coherent-* attribute\n * @property {Element} currentTarget - Same as `target`: the element whose handler runs\n * @property {boolean} defaultPrevented - Whether the default action was prevented\n * @property {boolean} propagationStopped - Whether a handler stopped delegated propagation\n * @property {function(): void} preventDefault - Delegates to originalEvent.preventDefault()\n * @property {function(): void} stopPropagation - Stops delegation to ancestor handlers and the native event\n * @property {function(): void} stopImmediatePropagation - Like stopPropagation, also for other native listeners\n * @property {function|null} component - The component function (if available)\n * @property {object|null} state - Current component state (if available)\n * @property {function|null} setState - State setter function (if available)\n * @property {object|null} props - Component props (if available)\n */\n\n/**\n * Wrap a native DOM event with component context\n *\n * @param {Event} originalEvent - The native DOM event\n * @param {Element} target - The element that matched the data attribute selector\n * @param {object|null} componentRef - Optional component reference object\n * @param {function} [componentRef.component] - The component function\n * @param {object} [componentRef.state] - Current component state\n * @param {function} [componentRef.setState] - State setter function\n * @param {object} [componentRef.props] - Component props\n * @returns {CoherentEvent} Wrapped event with component context\n */\nexport function wrapEvent(originalEvent, target, componentRef = null) {\n const wrapped = {\n // Native event access\n originalEvent,\n type: originalEvent?.type,\n target,\n currentTarget: target,\n propagationStopped: false,\n\n get defaultPrevented() {\n return Boolean(originalEvent?.defaultPrevented);\n },\n\n // Delegate common methods\n preventDefault() {\n originalEvent.preventDefault();\n },\n\n stopPropagation() {\n wrapped.propagationStopped = true;\n originalEvent.stopPropagation();\n },\n\n stopImmediatePropagation() {\n wrapped.propagationStopped = true;\n if (typeof originalEvent.stopImmediatePropagation === 'function') {\n originalEvent.stopImmediatePropagation();\n } else {\n originalEvent.stopPropagation();\n }\n },\n\n // Component context (null if no componentRef provided)\n component: componentRef?.component ?? null,\n state: componentRef?.state ?? null,\n setState: componentRef?.setState ?? null,\n props: componentRef?.props ?? null,\n };\n return wrapped;\n}\n", "/**\n * Event Delegation for Coherent.js\n *\n * Document-level event delegation that routes events to handlers via\n * data-coherent-{eventType} attributes. This ensures event handlers\n * survive DOM updates since they're registered by ID, not by element.\n */\n\nimport { handlerRegistry as defaultRegistry } from './registry.js';\nimport { wrapEvent } from './wrapper.js';\n\n/** Event types listened for from initialize(); others are added on demand. */\nconst DEFAULT_EVENT_TYPES = [\n 'click',\n 'change',\n 'input',\n 'submit',\n 'focus',\n 'blur',\n 'keydown',\n 'keyup',\n 'keypress',\n];\n\n/**\n * Events that do not bubble. They are caught in the capture phase and only\n * reach a handler on the event's own target.\n */\nconst NON_BUBBLING_EVENTS = new Set([\n 'mouseenter', 'mouseleave', 'pointerenter', 'pointerleave',\n 'load', 'error', 'abort', 'scroll', 'toggle', 'invalid', 'cancel', 'close',\n 'play', 'pause', 'ended', 'playing', 'waiting', 'seeked', 'seeking',\n 'canplay', 'canplaythrough', 'durationchange', 'emptied', 'loadeddata',\n 'loadedmetadata', 'loadstart', 'progress', 'ratechange', 'stalled',\n 'suspend', 'timeupdate', 'volumechange',\n]);\n\n/**\n * Focus events do not bubble either, but a handler on a container is expected\n * to hear its fields' focus (like focusin), so they are captured and then\n * walked up the tree like bubbling events.\n */\nconst CAPTURED_BUBBLING_EVENTS = new Set(['focus', 'blur']);\n\n/**\n * Scroll-blocking events are registered passive, as browsers do by default at\n * document level, so delegation never delays scrolling. preventDefault() is\n * ignored for them; every other type is registered non-passive.\n */\nconst PASSIVE_EVENTS = new Set(['touchstart', 'touchmove', 'wheel', 'mousewheel', 'scroll']);\n\n/**\n * Listener options for a delegated event type.\n * @param {string} eventType\n * @returns {{capture: boolean, passive: boolean}}\n */\nexport function getListenerOptions(eventType) {\n return {\n capture: CAPTURED_BUBBLING_EVENTS.has(eventType) || NON_BUBBLING_EVENTS.has(eventType),\n passive: PASSIVE_EVENTS.has(eventType),\n };\n}\n\n/**\n * EventDelegation class\n * Manages document-level event listeners and routes to registered handlers\n */\nexport class EventDelegation {\n /**\n * @param {import('./registry.js').HandlerRegistry} [registry] - Handler registry instance\n */\n constructor(registry = defaultRegistry) {\n this.registry = registry;\n this.initialized = false;\n this.root = null;\n this.boundHandlers = new Map();\n\n /**\n * Event types listened for by initialize(). Any other type is added the\n * first time listen() is called for it.\n */\n this.eventTypes = [...DEFAULT_EVENT_TYPES];\n }\n\n /**\n * Initialize event delegation by attaching listeners to the root element\n * @param {Document|Element} [root=document] - Root element for event delegation\n */\n initialize(root = typeof document !== 'undefined' ? document : null) {\n if (this.initialized) {\n return;\n }\n\n if (!root) {\n // No DOM available (SSR context)\n return;\n }\n\n this.root = root;\n this.initialized = true;\n\n for (const eventType of this.eventTypes) {\n this.attach(eventType);\n }\n }\n\n /**\n * Make sure events of `eventType` are delegated. Called by hydrate() for\n * every event type a component handles, so types beyond the defaults\n * (dblclick, mouseenter, pointerdown, ...) work too.\n * @param {string} eventType - DOM event type, e.g. 'dblclick'\n */\n listen(eventType) {\n if (!this.eventTypes.includes(eventType)) {\n this.eventTypes.push(eventType);\n }\n if (this.initialized) {\n this.attach(eventType);\n }\n }\n\n /** @private */\n attach(eventType) {\n if (this.boundHandlers.has(eventType)) {\n return;\n }\n\n const handler = (event) => this.handleEvent(event, eventType);\n const options = getListenerOptions(eventType);\n\n this.root.addEventListener(eventType, handler, options);\n this.boundHandlers.set(eventType, { handler, options });\n }\n\n /**\n * Handle a delegated event\n *\n * Runs the handler of the nearest element carrying\n * `data-coherent-{eventType}`, then those of its ancestors, like native\n * bubbling, until a handler stops propagation. Non-bubbling events only run\n * a handler on the target itself.\n *\n * @param {Event} event - The DOM event\n * @param {string} eventType - The type of event (click, change, etc.)\n */\n handleEvent(event, eventType) {\n const target = event.target;\n if (!target || typeof target.closest !== 'function') {\n return;\n }\n\n const attrName = `data-coherent-${eventType}`;\n const selector = `[${attrName}]`;\n const bubbles = !NON_BUBBLING_EVENTS.has(eventType);\n\n let current = target.closest(selector);\n if (!bubbles && current !== target) {\n return;\n }\n\n while (current) {\n const handlerId = current.getAttribute(attrName);\n const entry = handlerId ? this.registry.get(handlerId) : undefined;\n\n if (entry) {\n // Wrap the event with component context and call the handler\n const wrappedEvent = wrapEvent(event, current, entry.componentRef);\n entry.handler(wrappedEvent);\n\n if (wrappedEvent.propagationStopped || event.cancelBubble === true) {\n return;\n }\n }\n\n if (!bubbles) {\n return;\n }\n\n const parent = current.parentElement;\n if (!parent || typeof parent.closest !== 'function' || !this.isWithinRoot(parent)) {\n return;\n }\n current = parent.closest(selector);\n }\n }\n\n /** @private */\n isWithinRoot(element) {\n const root = this.root;\n if (!root || root.nodeType === 9 || typeof root.contains !== 'function') {\n return true;\n }\n return root.contains(element);\n }\n\n /**\n * Destroy the event delegation system\n * Removes all listeners and clears the registry\n */\n destroy() {\n if (!this.initialized || !this.root) {\n return;\n }\n\n // Remove all event listeners\n for (const [eventType, { handler, options }] of this.boundHandlers) {\n this.root.removeEventListener(eventType, handler, options);\n }\n\n this.boundHandlers.clear();\n this.registry.clear();\n this.eventTypes = [...DEFAULT_EVENT_TYPES];\n this.initialized = false;\n this.root = null;\n }\n\n /**\n * Check if the delegation system is initialized\n * @returns {boolean} True if initialized\n */\n isInitialized() {\n return this.initialized;\n }\n}\n\n/**\n * Singleton event delegation instance\n * Use this for global event delegation\n */\nexport const eventDelegation = new EventDelegation();\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/events/index.js
CHANGED
package/dist/hmr.js
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
CleanupTracker,
|
|
3
|
+
ConnectionIndicator,
|
|
4
|
+
ErrorOverlay,
|
|
5
|
+
HMRClient,
|
|
6
|
+
ModuleTracker,
|
|
7
|
+
StateCapturer,
|
|
8
|
+
cleanupTracker,
|
|
9
|
+
connectionIndicator,
|
|
10
|
+
createHotContext,
|
|
11
|
+
errorOverlay,
|
|
12
|
+
escapeHtml,
|
|
13
|
+
formatCodeFrame,
|
|
14
|
+
hmrClient,
|
|
15
|
+
moduleTracker,
|
|
16
|
+
stateCapturer
|
|
17
|
+
} from "./chunk-EAOAAY2X.js";
|
|
18
|
+
export {
|
|
19
|
+
CleanupTracker,
|
|
20
|
+
ConnectionIndicator,
|
|
21
|
+
ErrorOverlay,
|
|
22
|
+
HMRClient,
|
|
23
|
+
ModuleTracker,
|
|
24
|
+
StateCapturer,
|
|
25
|
+
cleanupTracker,
|
|
26
|
+
connectionIndicator,
|
|
27
|
+
createHotContext,
|
|
28
|
+
errorOverlay,
|
|
29
|
+
escapeHtml,
|
|
30
|
+
formatCodeFrame,
|
|
31
|
+
hmrClient,
|
|
32
|
+
moduleTracker,
|
|
33
|
+
stateCapturer
|
|
34
|
+
};
|
|
5
35
|
//# sourceMappingURL=hmr.js.map
|
package/dist/hmr.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": [
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"mappings": "
|
|
3
|
+
"sources": [],
|
|
4
|
+
"sourcesContent": [],
|
|
5
|
+
"mappings": "",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|