@livetemplate/client 0.8.27 → 0.8.28

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../livetemplate-client.ts", "../node_modules/morphdom/dist/morphdom-esm.js", "../constants.ts", "../dom/focus-manager.ts", "../dom/reactive-attributes.ts", "../dom/directives.ts", "../utils/rate-limit.ts", "../utils/lvt-selector.ts", "../dom/event-delegation.ts", "../dom/link-interceptor.ts", "../dom/observer-manager.ts", "../dom/loading-indicator.ts", "../dom/form-disabler.ts", "../dom/invoker-polyfill.ts", "../state/tree-renderer.ts", "../state/form-lifecycle-manager.ts", "../state/change-auto-wirer.ts", "../transport/websocket.ts", "../upload/s3-uploader.ts", "../upload/upload-handler.ts", "../utils/logger.ts", "../utils/testing.ts"],
4
- "sourcesContent": ["/**\n * LiveTemplate TypeScript Client\n *\n * Reconstructs HTML from tree-based updates using cached static structure,\n * following the Phoenix LiveView optimization approach.\n */\n\nimport morphdom from \"morphdom\";\nimport { FocusManager } from \"./dom/focus-manager\";\nimport {\n handleAnimateDirectives,\n handleHighlightDirectives,\n handleScrollDirectives,\n handleToastDirectives,\n setupToastClickOutside,\n setupFxDOMEventTriggers,\n teardownFxDOMEventTriggers,\n setupFxLifecycleListeners,\n teardownFxLifecycleListeners,\n} from \"./dom/directives\";\nimport { EventDelegator } from \"./dom/event-delegation\";\nimport { LinkInterceptor } from \"./dom/link-interceptor\";\nimport { ObserverManager } from \"./dom/observer-manager\";\nimport { LoadingIndicator } from \"./dom/loading-indicator\";\nimport { FormDisabler } from \"./dom/form-disabler\";\nimport { setupReactiveAttributeListeners } from \"./dom/reactive-attributes\";\nimport { setupInvokerPolyfill } from \"./dom/invoker-polyfill\";\nimport { TreeRenderer } from \"./state/tree-renderer\";\nimport { FormLifecycleManager } from \"./state/form-lifecycle-manager\";\nimport { ChangeAutoWirer } from \"./state/change-auto-wirer\";\nimport { WebSocketManager } from \"./transport/websocket\";\nimport { UploadHandler } from \"./upload/upload-handler\";\nimport type {\n UploadProgressMessage,\n UploadStartResponse,\n} from \"./upload/types\";\nimport type {\n LiveTemplateClientOptions,\n ResponseMetadata,\n TreeNode,\n UpdateResponse,\n UpdateResult,\n} from \"./types\";\nimport { createLogger, Logger } from \"./utils/logger\";\nexport { loadAndApplyUpdate, compareHTML } from \"./utils/testing\";\nexport { setupReactiveAttributeListeners } from \"./dom/reactive-attributes\";\n\nexport class LiveTemplateClient {\n private readonly treeRenderer: TreeRenderer;\n private readonly focusManager: FocusManager;\n private readonly logger: Logger;\n private lvtId: string | null = null;\n\n // Transport properties\n private webSocketManager: WebSocketManager;\n public ws: WebSocket | null = null;\n private wrapperElement: Element | null = null;\n private options: LiveTemplateClientOptions;\n private useHTTP: boolean = false; // True when WebSocket is unavailable\n private sessionCookie: string | null = null; // For HTTP mode session tracking\n\n // Rate limiting: cache of debounced/throttled handlers per element+eventType\n private rateLimitedHandlers: WeakMap<Element, Map<string, Function>> =\n new WeakMap();\n\n private eventDelegator: EventDelegator;\n private linkInterceptor: LinkInterceptor;\n private observerManager: ObserverManager;\n private formLifecycleManager: FormLifecycleManager;\n private loadingIndicator: LoadingIndicator;\n private formDisabler: FormDisabler;\n private uploadHandler: UploadHandler;\n private changeAutoWirer: ChangeAutoWirer;\n\n // Initialization tracking for loading indicator\n private isInitialized: boolean = false;\n\n // Message tracking for deterministic E2E testing\n private messageCount: number = 0;\n\n // Cross-handler navigation: track the latest in-flight connect() so a\n // subsequent navigation can supersede an earlier one. Incremented on\n // each cross-handler navigation; handlers check the epoch to avoid\n // applying stale connection results.\n private navigationEpoch: number = 0;\n\n // Override for the live URL used by HTTP send and multipart methods.\n // Updated on cross-handler navigation so HTTP requests go to the new\n // handler path. When null, falls back to options.liveUrl. We avoid\n // mutating the options object so callers holding a reference don't\n // observe side-effects.\n private liveUrlOverride: string | null = null;\n\n constructor(options: LiveTemplateClientOptions = {}) {\n const { logger: providedLogger, logLevel, debug, ...restOptions } = options;\n const resolvedLevel = logLevel ?? (debug ? \"debug\" : \"info\");\n const baseLogger = providedLogger ?? createLogger({ level: resolvedLevel });\n\n if (providedLogger) {\n if (logLevel) {\n providedLogger.setLevel(logLevel);\n } else if (debug) {\n providedLogger.setLevel(\"debug\");\n }\n } else {\n baseLogger.setLevel(resolvedLevel);\n }\n\n this.logger = baseLogger.child(\"Client\");\n\n this.options = {\n autoReconnect: false, // Disable autoReconnect by default to avoid connection loops\n reconnectDelay: 1000,\n // liveUrl captures the current page URL (path + search) so the\n // initial WebSocket handshake reaches the server with the same\n // query params Mount saw on the HTTP GET. This is intentional \u2014\n // without the search, the WS-side Mount re-runs with empty data\n // and drifts state from the HTTP render.\n //\n // For *same-handler* SPA navigation (changing query params on\n // the same path), the client does NOT reconnect \u2014 instead it\n // sends an in-band {action:\"__navigate__\", data:<params>} message\n // over the existing WebSocket, and the server re-runs Mount with\n // the new data. See sendNavigate() and handleNavigationResponse()\n // for the SPA path. Cross-handler SPA navigation still does the\n // fetch-and-replaceChildren+reconnect dance.\n liveUrl: window.location.pathname + window.location.search,\n ...restOptions,\n };\n\n this.treeRenderer = new TreeRenderer(this.logger.child(\"TreeRenderer\"));\n this.focusManager = new FocusManager(this.logger.child(\"FocusManager\"));\n\n this.formLifecycleManager = new FormLifecycleManager();\n this.loadingIndicator = new LoadingIndicator();\n this.formDisabler = new FormDisabler();\n\n // Initialize upload handler\n this.uploadHandler = new UploadHandler(\n (message) => this.send(message),\n {\n chunkSize: 256 * 1024, // 256KB chunks\n onProgress: (entry) => {\n // Trigger DOM update to refresh upload progress\n if (this.wrapperElement) {\n this.wrapperElement.dispatchEvent(\n new CustomEvent(\"lvt:upload:progress\", {\n detail: { entry },\n })\n );\n }\n },\n onComplete: (uploadName, entries) => {\n this.logger.info(`Upload complete: ${uploadName}`, entries);\n if (this.wrapperElement) {\n this.wrapperElement.dispatchEvent(\n new CustomEvent(\"lvt:upload:complete\", {\n detail: { uploadName, entries },\n })\n );\n }\n },\n onError: (entry, error) => {\n this.logger.error(`Upload error for ${entry.id}:`, error);\n if (this.wrapperElement) {\n this.wrapperElement.dispatchEvent(\n new CustomEvent(\"lvt:upload:error\", {\n detail: { entry, error },\n })\n );\n }\n },\n }\n );\n\n this.eventDelegator = new EventDelegator(\n {\n getWrapperElement: () => this.wrapperElement,\n getRateLimitedHandlers: () => this.rateLimitedHandlers,\n parseValue: (value: string) => this.parseValue(value),\n send: (message: any) => this.send(message),\n sendHTTPMultipart: (form: HTMLFormElement, action: string, formData: FormData) =>\n this.sendHTTPMultipart(form, action, formData),\n setActiveSubmission: (\n form: HTMLFormElement | null,\n button: HTMLButtonElement | null,\n originalButtonText: string | null\n ) =>\n this.formLifecycleManager.setActiveSubmission(\n form,\n button,\n originalButtonText\n ),\n getWebSocketReadyState: () => this.webSocketManager.getReadyState(),\n triggerPendingUploads: (uploadName: string) =>\n this.uploadHandler.triggerPendingUploads(uploadName),\n },\n this.logger.child(\"EventDelegator\")\n );\n\n this.linkInterceptor = new LinkInterceptor(\n {\n getWrapperElement: () => this.wrapperElement,\n handleNavigationResponse: (html: string) => this.handleNavigationResponse(html),\n sendNavigate: (href: string) => this.sendNavigate(href),\n // Only take the in-band fast path when the WS is actually OPEN.\n // If WS is CONNECTING or CLOSED, falling through to the normal fetch\n // path is safer: pushState fires after the fetch resolves (not before),\n // so the browser URL never gets ahead of server state.\n canSendNavigate: () =>\n !this.useHTTP &&\n this.webSocketManager.getReadyState() === 1 /* WebSocket.OPEN */,\n },\n this.logger.child(\"LinkInterceptor\")\n );\n\n this.observerManager = new ObserverManager(\n {\n getWrapperElement: () => this.wrapperElement,\n send: (message: any) => this.send(message),\n },\n this.logger.child(\"ObserverManager\")\n );\n\n this.changeAutoWirer = new ChangeAutoWirer(\n {\n getWrapperElement: () => this.wrapperElement,\n send: (message) => this.send(message),\n },\n this.logger.child(\"ChangeAutoWirer\")\n );\n\n this.webSocketManager = new WebSocketManager({\n options: this.options,\n logger: this.logger.child(\"Transport\"),\n onConnected: () => {\n this.ws = this.webSocketManager.getSocket();\n this.logger.info(\"WebSocket connected\");\n\n // Clear flash-related query params from URL to prevent stale flash on reload\n // This handles the redirect pattern: /auth?error=invalid_credentials\n this.clearFlashQueryParams();\n\n this.options.onConnect?.();\n this.wrapperElement?.dispatchEvent(new Event(\"lvt:connected\"));\n },\n onDisconnected: () => {\n this.ws = null;\n this.logger.info(\"WebSocket disconnected\");\n this.options.onDisconnect?.();\n this.wrapperElement?.dispatchEvent(new Event(\"lvt:disconnected\"));\n },\n onMessage: (response, event) => {\n this.handleWebSocketPayload(response, event);\n },\n onReconnectAttempt: () => {\n this.logger.info(\"Attempting to reconnect...\");\n },\n onError: (error) => {\n this.logger.error(\"WebSocket error:\", error);\n this.options.onError?.(error);\n },\n });\n }\n\n /**\n * Auto-initialize when DOM is ready\n * Called automatically when script loads\n */\n static autoInit(): void {\n const autoInitLogger = createLogger({ scope: \"Client:autoInit\" });\n const init = () => {\n const wrapper = document.querySelector(\"[data-lvt-id]\");\n if (wrapper) {\n const client = new LiveTemplateClient();\n client.wrapperElement = wrapper;\n\n // Check if loading indicator should be shown\n const shouldShowLoading =\n wrapper.getAttribute(\"data-lvt-loading\") === \"true\";\n if (shouldShowLoading) {\n client.loadingIndicator.show();\n client.formDisabler.disable(client.wrapperElement);\n }\n\n client.connect().catch((error) => {\n autoInitLogger.error(\"Auto-initialization connect failed:\", error);\n });\n\n // Expose as global for programmatic access\n (window as any).liveTemplateClient = client;\n }\n };\n\n // Initialize when DOM is ready\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", init);\n } else {\n init();\n }\n }\n\n /**\n * Handle server-sent updates delivered via WebSocket or HTTP fallback.\n */\n private handleWebSocketPayload(\n response: UpdateResponse,\n event?: MessageEvent<string>\n ): void {\n // Check if this is an upload-specific message\n const uploadMessage = response as any;\n if (uploadMessage.type === \"upload_progress\") {\n this.uploadHandler.handleProgressMessage(\n uploadMessage as UploadProgressMessage\n );\n return;\n }\n\n // Check if this is an upload_start response\n if (uploadMessage.upload_name && uploadMessage.entries) {\n const startResponse = uploadMessage as UploadStartResponse;\n // Handle upload start response with error handling\n try {\n this.handleUploadStartResponse(startResponse);\n } catch (error) {\n this.logger.error(\"Error handling upload start response:\", error);\n }\n return;\n }\n\n // Check if this is an upload_complete response\n if (uploadMessage.upload_name && uploadMessage.hasOwnProperty('success')) {\n // UploadCompleteResponse - just log it, no tree update needed\n if (uploadMessage.success) {\n this.logger.info(`Upload complete: ${uploadMessage.upload_name}`);\n } else {\n this.logger.error(`Upload failed: ${uploadMessage.upload_name}`, uploadMessage.error);\n }\n return;\n }\n\n if (!this.isInitialized) {\n this.loadingIndicator.hide();\n this.formDisabler.enable(this.wrapperElement);\n if (\n this.wrapperElement &&\n this.wrapperElement.hasAttribute(\"data-lvt-loading\")\n ) {\n this.wrapperElement.removeAttribute(\"data-lvt-loading\");\n }\n this.isInitialized = true;\n }\n\n if (this.wrapperElement) {\n if (response.meta?.capabilities) {\n this.changeAutoWirer.setCapabilities(response.meta.capabilities);\n }\n // Analyze statics before updateDOM so wireElements() inside updateDOM\n // has bound fields to work with. Additive: new fields from conditionally\n // rendered templates are detected as they appear in updates.\n this.changeAutoWirer.analyzeStatics(response.tree);\n\n this.updateDOM(this.wrapperElement, response.tree, response.meta);\n this.messageCount++;\n\n this.wrapperElement.dispatchEvent(\n new CustomEvent(\"lvt:updated\", {\n detail: {\n messageCount: this.messageCount,\n action: response.meta?.action,\n success: response.meta?.success,\n },\n })\n );\n }\n }\n\n /**\n * Connect to WebSocket and start receiving updates\n * @param wrapperSelector - CSS selector for the LiveTemplate wrapper (defaults to '[data-lvt-id]')\n */\n async connect(wrapperSelector: string = \"[data-lvt-id]\"): Promise<void> {\n // Find the wrapper element\n this.wrapperElement = document.querySelector(wrapperSelector);\n if (!this.wrapperElement) {\n throw new Error(\n `LiveTemplate wrapper not found with selector: ${wrapperSelector}`\n );\n }\n\n this.webSocketManager.disconnect();\n\n const connectionResult = await this.webSocketManager.connect();\n this.useHTTP = !connectionResult.usingWebSocket;\n\n if (this.useHTTP) {\n this.ws = null;\n this.logger.info(\"WebSocket not available, using HTTP mode\");\n this.options.onConnect?.();\n if (connectionResult.initialState && this.wrapperElement) {\n this.handleWebSocketPayload(connectionResult.initialState);\n }\n }\n // Set up event delegation for lvt-* attributes\n this.eventDelegator.setupEventDelegation();\n\n // Set up window-* event delegation\n this.eventDelegator.setupWindowEventDelegation();\n\n // Set up click-away delegation\n this.eventDelegator.setupClickAwayDelegation();\n\n // Set up DOM event trigger delegation for lvt-el:*:on:{event} attributes\n this.eventDelegator.setupDOMEventTriggerDelegation();\n\n // Set up click-outside listener for client-managed toast stack\n setupToastClickOutside();\n\n // Set up focus trap delegation for lvt-focus-trap attribute\n this.eventDelegator.setupFocusTrapDelegation();\n\n // Set up autofocus delegation for lvt-autofocus attribute\n this.eventDelegator.setupAutofocusDelegation();\n\n // Set up link interception for SPA navigation\n this.linkInterceptor.setup(this.wrapperElement);\n\n // Set up reactive attribute listeners for lvt-el:*:on:* attributes\n setupReactiveAttributeListeners();\n\n setupInvokerPolyfill();\n\n // Set up lifecycle listeners for lvt-fx:*:on:{lifecycle} attributes\n setupFxLifecycleListeners(this.wrapperElement);\n\n // Initialize focus tracking\n this.focusManager.attach(this.wrapperElement);\n\n // Set up infinite scroll observers\n this.observerManager.setupInfiniteScrollObserver();\n this.observerManager.setupInfiniteScrollMutationObserver();\n }\n\n /**\n * Disconnect from WebSocket\n */\n disconnect(): void {\n this.webSocketManager.disconnect();\n this.ws = null;\n this.useHTTP = false;\n this.eventDelegator.teardownDOMEventTriggerDelegation();\n if (this.wrapperElement) {\n teardownFxDOMEventTriggers(this.wrapperElement);\n teardownFxLifecycleListeners(this.wrapperElement);\n }\n this.resetSessionState();\n }\n\n // resetSessionState clears all per-session manager state. Called by both\n // disconnect() (with additional transport/event teardown) and reset().\n // Essential for cross-handler SPA navigation: without treeRenderer.reset(),\n // accumulated tree state from the old handler merges into the new one.\n //\n // Note on isInitialized: setting this to false is intentional and is\n // a behavioral change from the prior reset() which left it sticky.\n // The prior behavior was an inconsistency \u2014 \"reset\" that didn't\n // actually put the client in a pre-init state. After reset, the next\n // payload is treated as an initial render: loading indicator shows,\n // forms are enabled, data-lvt-loading is removed. This matches the\n // post-disconnect contract and the documented \"useful for testing\"\n // intent \u2014 tests can observe the init transition a second time.\n private resetSessionState(): void {\n this.treeRenderer.reset();\n this.focusManager.reset();\n this.observerManager.teardown();\n this.changeAutoWirer.teardown();\n this.formLifecycleManager.reset();\n this.loadingIndicator.hide();\n this.formDisabler.enable(this.wrapperElement);\n this.lvtId = null;\n this.isInitialized = false;\n }\n\n /**\n * Clear flash-related query parameters (error, success) from the URL.\n * This prevents stale flash messages from reappearing on page reload.\n * Uses history.replaceState to update URL without triggering navigation.\n */\n private clearFlashQueryParams(): void {\n const url = new URL(window.location.href);\n const flashParams = [\"error\", \"success\"];\n let hasFlashParams = false;\n\n for (const param of flashParams) {\n if (url.searchParams.has(param)) {\n url.searchParams.delete(param);\n hasFlashParams = true;\n }\n }\n\n if (hasFlashParams) {\n this.logger.debug(\"Clearing flash query params from URL\");\n window.history.replaceState(null, \"\", url.toString());\n }\n }\n\n /**\n * Determine whether the client finished its initial load and has an active transport.\n */\n isReady(): boolean {\n const wrapper = this.wrapperElement;\n\n if (!wrapper || wrapper.hasAttribute(\"data-lvt-loading\")) {\n return false;\n }\n\n if (this.useHTTP) {\n return true;\n }\n\n const readyState = this.webSocketManager.getReadyState();\n return readyState === 1; // WebSocket.OPEN = 1\n }\n\n /**\n * Send a message to the server via WebSocket or HTTP\n * @param message - Message to send (will be JSON stringified)\n */\n send(message: any): void {\n const readyState = this.webSocketManager.getReadyState();\n\n if (this.logger.isDebugEnabled()) {\n this.logger.debug(\"send() invoked\", {\n message,\n useHTTP: this.useHTTP,\n hasWebSocket: readyState !== undefined,\n readyState,\n });\n }\n\n if (this.useHTTP) {\n this.logger.debug(\"Using HTTP mode for send\");\n this.sendHTTP(message);\n } else if (readyState === 1) { // WebSocket.OPEN = 1\n this.logger.debug(\"Sending via WebSocket\");\n this.webSocketManager.send(JSON.stringify(message));\n } else if (readyState !== undefined) {\n this.logger.warn(\n `WebSocket not ready (state: ${readyState}), using HTTP fallback`\n );\n this.sendHTTP(message);\n } else {\n this.logger.error(\"No transport available\");\n }\n }\n\n /**\n * Get the current live URL for HTTP methods. Falls back to\n * options.liveUrl when no override is set. Cross-handler navigation\n * uses setLiveUrl() to update the override without mutating options.\n */\n private getLiveUrl(): string {\n return this.liveUrlOverride || this.options.liveUrl || \"/live\";\n }\n\n /**\n * Send an in-band navigate message over the existing WebSocket.\n *\n * This is the client side of the same-handler SPA navigation flow.\n * Rather than disconnect + reconnect to land a URL change (which is\n * what cross-handler nav does and what the old same-handler path\n * silently skipped), we parse the target URL's query params into a\n * data map and send {action: \"__navigate__\", data: params}. The\n * server special-cases this action name in its event loop (see\n * livetemplate/mount.go) and re-runs Mount with the new data without\n * tearing down the connection.\n *\n * Equivalent to Phoenix LiveView's live_patch / handle_params:\n * path-level identity for the socket, Mount-level re-projection for\n * query-string changes.\n *\n * @param href - The target URL to navigate to. Only the search params\n * are consumed; the pathname is assumed to match the\n * current page (caller checks same-handler first).\n */\n private sendNavigate(href: string): boolean {\n const url = new URL(href, window.location.origin);\n const data: Record<string, string> = {};\n // Note: duplicate keys (e.g. ?tag=a&tag=b) are last-write-wins here.\n // LiveTemplate routes use scalar string params by convention. Routes\n // that need repeated params should not use sendNavigate directly.\n const seenKeys = new Set<string>();\n url.searchParams.forEach((v, k) => {\n if (seenKeys.has(k)) {\n this.logger.warn(\"sendNavigate: duplicate query param key \u2014 last value wins; server may receive incomplete data\", { key: k, href });\n }\n seenKeys.add(k);\n data[k] = v;\n });\n\n const newLiveUrl = url.pathname + url.search;\n\n // __navigate__ is a WebSocket-only in-band action \u2014 only call send()\n // when the socket is actually OPEN.\n //\n // Note: this.useHTTP is not checked here because sendNavigate() is only\n // reachable when canSendNavigate() returns true (i.e. !this.useHTTP and\n // readyState === 1), enforced by LinkInterceptor. Checking useHTTP or\n // re-checking readyState here would be dead code in the normal call path.\n // The guard below is a defensive fallback for direct callers that bypass\n // canSendNavigate().\n if (this.webSocketManager.getReadyState() !== 1 /* WebSocket.OPEN */) {\n const readyState = this.webSocketManager.getReadyState();\n if (readyState === 3 /* CLOSED */) {\n this.logger.error(\n \"sendNavigate: WebSocket is CLOSED and autoReconnect may be disabled; \" +\n \"navigate message dropped. Reload or re-enable autoReconnect.\",\n { href }\n );\n } else {\n // CONNECTING (0) or CLOSING (2). autoReconnect defaults to false.\n if (!this.options.autoReconnect) {\n this.logger.error(\n \"sendNavigate: WS not open and autoReconnect is disabled; navigate may be permanently lost\",\n { href, readyState }\n );\n } else {\n this.logger.warn(\n \"sendNavigate: WS not open; browser URL is ahead of server state until reconnect\",\n { href, readyState }\n );\n }\n }\n return false;\n }\n\n // Socket is OPEN: commit the URL update and send the navigate message.\n // liveUrlOverride is updated here (not before the guard) so it only\n // advances when the message is actually sent \u2014 keeping it consistent\n // with window.location throughout.\n this.liveUrlOverride = newLiveUrl;\n this.webSocketManager.setLiveUrl(newLiveUrl);\n this.logger.debug(\"sendNavigate\", { href, data });\n this.send({ action: \"__navigate__\", data });\n return true;\n }\n\n /**\n * Send action via HTTP POST\n */\n private async sendHTTP(message: any): Promise<void> {\n try {\n const liveUrl = this.getLiveUrl();\n const response = await fetch(liveUrl, {\n method: \"POST\",\n credentials: \"include\", // Include cookies for session\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: JSON.stringify(message),\n });\n\n if (!response.ok) {\n throw new Error(`HTTP request failed: ${response.status}`);\n }\n\n // Handle the update response\n const updateResponse: UpdateResponse = await response.json();\n if (this.wrapperElement) {\n this.updateDOM(\n this.wrapperElement,\n updateResponse.tree,\n updateResponse.meta\n );\n }\n } catch (error) {\n this.logger.error(\"Failed to send HTTP request:\", error);\n }\n }\n\n /**\n * Send form with file inputs via HTTP POST multipart/form-data.\n * Used for Tier 1 file uploads where binary files are submitted via\n * HTTP fetch instead of WebSocket (avoids base64 encoding overhead).\n *\n * IMPORTANT: Callers must pass pre-captured form data (formData).\n * FormData must be built BEFORE setActiveSubmission disables the\n * form's fieldset \u2014 otherwise FormData would be empty because\n * disabled fieldsets exclude all child fields. The caller is also\n * responsible for setting the \"lvt-action\" entry; this method will\n * not mutate the passed FormData.\n */\n sendHTTPMultipart(form: HTMLFormElement, action: string, formData: FormData): void {\n this.doSendHTTPMultipart(form, action, formData);\n }\n\n private async doSendHTTPMultipart(\n form: HTMLFormElement,\n action: string,\n formData: FormData\n ): Promise<void> {\n try {\n const liveUrl = this.getLiveUrl();\n\n const response = await fetch(liveUrl, {\n method: \"POST\",\n credentials: \"include\",\n headers: {\n Accept: \"application/json\",\n // Do NOT set Content-Type \u2014 browser sets multipart boundary automatically\n },\n body: formData,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP multipart request failed: ${response.status}`);\n }\n\n const updateResponse: UpdateResponse = await response.json();\n if (this.wrapperElement) {\n this.updateDOM(\n this.wrapperElement,\n updateResponse.tree,\n updateResponse.meta\n );\n }\n } catch (error) {\n this.logger.error(\"Failed to send HTTP multipart request:\", error);\n }\n }\n\n /**\n * Handle navigation response from link interception.\n * Extracts the wrapper content from the full HTML page and replaces\n * the current wrapper content. Content comes from same-origin fetch\n * responses only (link interceptor skips external origins).\n *\n * Supports both same-handler navigation (same data-lvt-id) and\n * cross-handler navigation (different data-lvt-id). Cross-handler\n * navigation disconnects the old WebSocket, replaces the wrapper\n * content and ID, and reconnects to the new handler. The URL must\n * already be updated via pushState before this method is called\n * (done in LinkInterceptor.navigate) so the WebSocket connects to\n * the correct handler.\n */\n private handleNavigationResponse(html: string): void {\n if (!this.wrapperElement) return;\n\n const parser = new DOMParser();\n const doc = parser.parseFromString(html, \"text/html\");\n const oldId = this.wrapperElement.getAttribute(\"data-lvt-id\");\n\n // Update document title from the fetched page. Only apply if the\n // new title is non-empty \u2014 blanking the title would be surprising\n // and unhelpful.\n const newTitleText = doc.querySelector(\"title\")?.textContent;\n if (newTitleText) {\n document.title = newTitleText;\n }\n\n // sameWrapper: the fetched page has the same data-lvt-id as the current\n // wrapper. This means two different paths share the same handler ID.\n // handleNavigationResponse is only reached via LinkInterceptor for\n // cross-pathname navigations (same-pathname links are caught by the fast\n // path before a fetch is issued and handled via sendNavigate directly).\n // For cross-pathname same-ID, a full reconnect is correct: the server\n // must receive the new URL to re-run Mount on the right route. We fall\n // through to the newWrapper block below, which handles both same-ID and\n // different-ID handler switches identically.\n //\n // Same-pathname same-ID navigation (query-param change on the same route)\n // is covered exclusively by LinkInterceptor's fast path and navigate.test.ts.\n\n // Check for any handler wrapper (same-ID cross-path or different handler)\n const newWrapper = doc.querySelector(\"[data-lvt-id]\");\n if (newWrapper) {\n const newId = newWrapper.getAttribute(\"data-lvt-id\");\n // Guard: attribute exists (we queried by [data-lvt-id]) but could be empty\n if (!newId) {\n this.logger.warn(\"Cross-handler navigation: new wrapper has empty data-lvt-id\");\n window.location.reload();\n return;\n }\n\n // Clean up stale event listeners keyed to the old wrapper ID.\n // Each component knows its own listener keys, so we delegate.\n this.linkInterceptor.teardownForWrapper(oldId);\n this.eventDelegator.teardownForWrapper(oldId);\n\n // Supersede any previous in-flight connect() from an earlier navigation\n const myEpoch = ++this.navigationEpoch;\n\n this.disconnect();\n this.wrapperElement.setAttribute(\"data-lvt-id\", newId);\n this.wrapperElement.replaceChildren(\n ...Array.from(newWrapper.childNodes).map((n) => n.cloneNode(true))\n );\n\n // Set up event delegation and link interception immediately so the\n // new content has working listeners BEFORE the async connect() runs.\n // connect() will re-run these internally, which is safe: both setup\n // methods are idempotent \u2014 they remove any existing listener with\n // the same key before adding the new one. Calling them twice in\n // quick succession results in a single active listener per event\n // type per wrapper ID.\n this.eventDelegator.setupEventDelegation();\n this.linkInterceptor.setup(this.wrapperElement);\n\n // Scroll to top for cross-handler navigation\n window.scrollTo(0, 0);\n\n // Update the live URL used by HTTP methods AND the WebSocket\n // manager to derive the reconnect path. We use private overrides\n // on both so the caller-provided options object is never mutated.\n // Hash fragments are intentionally excluded \u2014 the WebSocket path\n // comes from pathname+search only.\n //\n // liveUrl convention: it is always the CURRENT PAGE PATH, not a\n // separate endpoint. Each LiveTemplate handler route is both the\n // HTTP page and the WebSocket endpoint for that handler, so the\n // page path and the WebSocket path are always the same. Apps that\n // need a different WebSocket endpoint should set `wsUrl`, which\n // takes precedence over `liveUrl` in WebSocketManager.\n //\n // Invariant: link-interceptor.ts calls pushState BEFORE invoking\n // handleNavigationResponse, so window.location here always\n // reflects the final target URL (not the previous one). This is\n // why liveUrlOverride is never stale for cross-pathname navigations\n // even if sendNavigate had previously set it to a same-pathname URL.\n const newLiveUrl =\n window.location.pathname + window.location.search;\n this.liveUrlOverride = newLiveUrl;\n this.webSocketManager.setLiveUrl(newLiveUrl);\n\n // In HTTP mode there is no persistent WebSocket connection \u2014 skip\n // connect() so we don't unexpectedly create a WebSocket. The DOM swap\n // and event re-setup above are sufficient for HTTP-mode apps; the next\n // user action will POST via the normal HTTP send path.\n if (!this.useHTTP) {\n // Reconnect to the new handler. The server sends an initial tree\n // that produces the same DOM as the fetched HTML.\n //\n // Escape the wrapper ID to defend against (pathological) server\n // responses with special characters that would break the\n // attribute selector. Only `\"` and `\\` need escaping inside a\n // double-quoted attribute value selector (`[attr=\"...\"]`), and\n // we prefer a manual escape over CSS.escape() which is not\n // available in jsdom test environments.\n //\n // Epoch semantics: the failure branch is guarded by the epoch\n // check to avoid stale reloads. The success branch has no work\n // to do \u2014 there's nothing for handleNavigationResponse to undo\n // on success.\n //\n // Known limitation: if two cross-handler navigations run in rapid\n // succession (A then B), A's connect() might still be executing\n // its post-await setup (useHTTP assignment, initial state\n // rendering, event delegation) when B starts. Because there's\n // only one WebSocketManager transport at a time, B's disconnect()\n // kills A's in-flight transport, and B's setup happens on the\n // wrapper with B's ID. If A's post-await code runs AFTER B sets\n // the wrapper ID, A's querySelector lookup would already be\n // stale (it captured the wrapper synchronously before the await).\n // A true fix requires making connect() itself cancellable with\n // an AbortSignal, which is out of scope for this PR. In practice,\n // two successive SPA navigations within a single event loop tick\n // are rare, and the idempotent setup methods minimize fallout.\n const escapedId = newId.replace(/[\\\\\"]/g, \"\\\\$&\");\n const selector = `[data-lvt-id=\"${escapedId}\"]`;\n this.connect(selector).catch((err) => {\n if (myEpoch !== this.navigationEpoch) return;\n this.logger.error(\"Cross-handler reconnect failed:\", err);\n window.location.reload();\n });\n }\n return;\n }\n\n // Non-LiveTemplate page \u2014 disconnect the old WebSocket (it's pointing\n // to a handler whose DOM is about to be replaced) and tear down the\n // old listeners keyed to the previous wrapper ID, then use body\n // content fallback.\n this.linkInterceptor.teardownForWrapper(oldId);\n this.eventDelegator.teardownForWrapper(oldId);\n this.disconnect();\n const body = doc.querySelector(\"body\");\n if (body) {\n this.wrapperElement.replaceChildren(\n ...Array.from(body.childNodes).map((n) => n.cloneNode(true))\n );\n }\n this.eventDelegator.setupEventDelegation();\n this.linkInterceptor.setup(this.wrapperElement);\n }\n\n /**\n * Parse a string value into appropriate type (number, boolean, or string)\n * @param value - String value to parse\n * @returns Parsed value with correct type\n */\n private parseValue(value: string): any {\n // Trim once for consistent handling\n const trimmed = value.trim();\n\n // Try to parse as number\n const num = parseFloat(trimmed);\n if (!isNaN(num)) {\n // Check range FIRST - large integers (like UnixNano timestamps) must stay as strings\n // to preserve precision. JavaScript's Number can only safely represent integers\n // up to 2^53-1 (MAX_SAFE_INTEGER = 9,007,199,254,740,991).\n if (Number.isInteger(num) && Math.abs(num) > Number.MAX_SAFE_INTEGER) {\n return trimmed;\n }\n // Only convert to number if string representation matches (no precision loss)\n if (trimmed === num.toString()) {\n return num;\n }\n }\n\n // Try to parse as boolean\n if (value === \"true\") return true;\n if (value === \"false\") return false;\n\n // Return as string\n return value;\n }\n\n /**\n * Apply an update to the current state and reconstruct HTML\n * @param update - Tree update object from LiveTemplate server\n * @returns Reconstructed HTML and whether anything changed\n */\n applyUpdate(update: TreeNode): UpdateResult {\n return this.treeRenderer.applyUpdate(update);\n }\n\n /**\n * Apply updates to existing HTML using morphdom for efficient DOM updates\n * @param existingHTML - Current full HTML document\n * @param update - Tree update object from LiveTemplate server\n * @returns Updated HTML content\n */\n applyUpdateToHTML(existingHTML: string, update: TreeNode): string {\n // Apply the update to our internal state\n const result = this.applyUpdate(update);\n\n // Extract lvt-id from existing HTML if we don't have it\n if (!this.lvtId) {\n const match = existingHTML.match(/data-lvt-id=\"([^\"]+)\"/);\n if (match) {\n this.lvtId = match[1];\n }\n }\n\n // The new tree includes complete HTML structure, so we can reconstruct properly\n const innerContent = result.html;\n\n // Find where to insert the reconstructed content\n const bodyMatch = existingHTML.match(/<body>([\\s\\S]*?)<\\/body>/);\n if (!bodyMatch) {\n return existingHTML;\n }\n\n // Replace the body content with our reconstructed HTML\n // We need to preserve the wrapper div with data-lvt-id\n const wrapperStart = `<div data-lvt-id=\"${this.lvtId || \"lvt-unknown\"}\">`;\n const wrapperEnd = \"</div>\";\n const newBodyContent = wrapperStart + innerContent + wrapperEnd;\n\n return existingHTML.replace(\n /<body>[\\s\\S]*?<\\/body>/,\n `<body>${newBodyContent}</body>`\n );\n }\n\n /**\n * Update a live DOM element with new tree data\n * @param element - DOM element containing the LiveTemplate content (the wrapper div)\n * @param update - Tree update object from LiveTemplate server\n * @param meta - Optional metadata about the update (action, success, errors)\n */\n updateDOM(element: Element, update: TreeNode, meta?: ResponseMetadata): void {\n // Apply update to internal state and get reconstructed HTML\n const result = this.applyUpdate(update);\n\n // Helper to recursively check if there are any statics in the tree\n const hasStaticsInTree = (node: any): boolean => {\n if (!node || typeof node !== \"object\") return false;\n if (node.s && Array.isArray(node.s)) return true;\n return Object.values(node).some((v) => hasStaticsInTree(v));\n };\n\n if (!result.changed && !hasStaticsInTree(update)) {\n // No changes detected and no statics in update, skip morphdom\n return;\n }\n\n // Create a temporary wrapper to hold the new content\n // We need to create a DOM element of the same type as 'element' to avoid browser HTML corrections\n // For example, if we put <tr> elements in a <div>, the browser strips them out\n const tempWrapper = document.createElement(element.tagName);\n\n if (this.logger.isDebugEnabled()) {\n this.logger.debug(\"[updateDOM] element.tagName:\", element.tagName);\n this.logger.debug(\n \"[updateDOM] result.html (first 500 chars):\",\n result.html.substring(0, 500)\n );\n this.logger.debug(\n \"[updateDOM] Has <table> tag:\",\n result.html.includes(\"<table>\")\n );\n this.logger.debug(\n \"[updateDOM] Has <tbody> tag:\",\n result.html.includes(\"<tbody>\")\n );\n this.logger.debug(\n \"[updateDOM] Has <tr> tag:\",\n result.html.includes(\"<tr\")\n );\n }\n\n // Use DOMParser when the HTML contains <script> tags. Browsers'\n // innerHTML parser handles scripts specially and can create phantom\n // duplicate DOM nodes after the closing tag. DOMParser doesn't have\n // this quirk because it returns a standalone document.\n //\n // Regex /<script[\\s>]/i is more precise than a bare \"<script\" string\n // match: it avoids false positives from words ending in \"script\" that\n // aren't a tag (e.g. \"noscript\"), while still matching <script>,\n // <script type=\"...\"> and <SCRIPT> case-insensitively.\n // Note: it can still match <script inside attribute values or HTML\n // comments \u2014 a false positive is harmless (DOMParser is always safe;\n // we just pay a small allocation cost for the parse).\n //\n // Wrap with the same tagName as the target element (not a hard-coded\n // <div>) so that DOMParser applies the correct HTML parsing rules.\n // Wrapping <tr>/<td>/<option> content in a <div> can trigger\n // browser re-parenting; using the real container tag avoids that.\n if (/<script[\\s>]/i.test(result.html)) {\n // Guard: <body> and <html> cannot be used as wrapper tags for\n // parseFromString \u2014 doc.body.firstElementChild would return the\n // first child of body, not the body itself, discarding the wrap.\n // Fall back to <div> for these edge cases; updateDOM is called on\n // the lvt wrapper div in practice, so this branch is defensive.\n const rawTag = element.tagName.toLowerCase();\n const wrapTag = (rawTag === \"body\" || rawTag === \"html\") ? \"div\" : rawTag;\n const parser = new DOMParser();\n const doc = parser.parseFromString(\n `<${wrapTag}>${result.html}</${wrapTag}>`,\n \"text/html\"\n );\n const root = doc.body.firstElementChild;\n if (root) {\n // Array.from snapshots the live NodeList before replaceChildren\n // starts moving nodes, keeping iteration stable.\n //\n // Note: DOMParser still re-parents bare table-cell content (tr/td\n // without surrounding table+tbody) even when wrapTag matches.\n // Slots rendered into table-cell elements with <script> tags are\n // an edge case; a follow-up can add a full-table wrapper for those.\n tempWrapper.replaceChildren(...Array.from(root.childNodes));\n } else {\n // root is null when the HTML parser produced no element child for\n // our wrapper tag (e.g. the wrapper was itself re-parented or the\n // fragment is text-only). Fall back to doc.body children \u2014 the\n // content is still present there, already parsed by DOMParser\n // without the script-duplication quirk that innerHTML triggers.\n this.logger.warn(\"[updateDOM] DOMParser: no wrapper root element; using doc.body children\");\n tempWrapper.replaceChildren(...Array.from(doc.body.childNodes));\n }\n } else {\n tempWrapper.innerHTML = result.html;\n }\n\n if (this.logger.isDebugEnabled()) {\n this.logger.debug(\n \"[updateDOM] tempWrapper.innerHTML (first 500 chars):\",\n tempWrapper.innerHTML.substring(0, 500)\n );\n this.logger.debug(\n \"[updateDOM] tempWrapper has <table>:\",\n tempWrapper.innerHTML.includes(\"<table>\")\n );\n this.logger.debug(\n \"[updateDOM] tempWrapper has <tbody>:\",\n tempWrapper.innerHTML.includes(\"<tbody>\")\n );\n this.logger.debug(\n \"[updateDOM] tempWrapper has <tr>:\",\n tempWrapper.innerHTML.includes(\"<tr\")\n );\n }\n\n // Use morphdom to efficiently update the element\n morphdom(element, tempWrapper, {\n childrenOnly: true, // Only update children, preserve the wrapper element itself\n getNodeKey: (node: any) => {\n // Use data-key or data-lvt-key for efficient reconciliation\n if (node.nodeType === 1) {\n return (\n node.getAttribute(\"data-key\") ||\n node.getAttribute(\"data-lvt-key\") ||\n undefined\n );\n }\n },\n onBeforeElUpdated: (fromEl, toEl) => {\n // Honour lvt-preserve: the client owns this element's entire\n // subtree and morphdom should never touch any of it.\n // Attributes, content, descendants all stay exactly as the DOM\n // currently has them. Equivalent to Phoenix LiveView's\n // phx-update=\"ignore\" and Turbo's data-turbo-permanent. Useful\n // for third-party widgets (maps, date pickers, charts) that\n // mutate their own DOM, and for scroll containers with\n // JS-managed scrollTop.\n //\n // We check toEl (the incoming server version) rather than fromEl\n // (the current DOM). This gives the server authority: if a later\n // render omits lvt-preserve, morphdom resumes updating the element.\n // Checking fromEl would make the attribute sticky in the DOM\n // forever once applied, preventing the server from ever removing it.\n //\n // For the subtler case of \"preserve my *attributes* but still\n // let children update\" (e.g. <details open>, <select> with\n // user-selected options), use lvt-preserve-attrs below.\n //\n // Priority: lvt-preserve is checked first and returns early; if\n // toEl has lvt-preserve, lvt-preserve-attrs on the same element\n // is never reached. lvt-preserve wins (full freeze > partial\n // attribute-only freeze).\n if (\n toEl.nodeType === Node.ELEMENT_NODE &&\n (toEl as Element).hasAttribute(\"lvt-preserve\")\n ) {\n return false;\n }\n\n // Honour lvt-preserve-attrs: the client owns this element's own\n // attributes (e.g. the <details open> state the user toggled),\n // but its children should still be diffed against the new tree.\n // This is the right fit for collapsible pickers where the\n // *container* state is user-managed but the *items* inside are\n // server-authored and must reflect the latest data.\n //\n // Implementation: copy fromEl's attributes that are missing from\n // toEl onto toEl before morphdom runs its attribute diff. This\n // makes morphdom see the user-owned attrs as \"already present\"\n // in the new version, so it doesn't remove them. Attributes that\n // exist in both fromEl and toEl take toEl's value (server wins\n // over client for attributes the template does control), which\n // preserves the ability to update className etc. on the same\n // element from the server.\n //\n // Limitation: only *missing* attributes from fromEl are copied.\n // If the server's toEl already has e.g. class=\"card\", that value\n // is used \u2014 JS-added classes (el.classList.add('open')) are\n // silently overwritten on the next diff. Use lvt-preserve-attrs\n // for attributes the server template does NOT set at all.\n //\n // We check toEl (the incoming server tree), not fromEl (the live\n // DOM), so the server has authority: removing lvt-preserve-attrs\n // from the template takes effect immediately on the next render,\n // consistent with how lvt-preserve works.\n if (\n toEl.nodeType === Node.ELEMENT_NODE &&\n (toEl as Element).hasAttribute(\"lvt-preserve-attrs\") &&\n fromEl.nodeType === Node.ELEMENT_NODE\n ) {\n const fromAttrs = (fromEl as Element).attributes;\n const toElement = toEl as Element;\n for (let i = 0; i < fromAttrs.length; i++) {\n const attr = fromAttrs[i];\n // Skip the preserve control attributes themselves so the\n // server can opt elements in/out across renders. If we\n // copied lvt-preserve-attrs back onto toEl, the server\n // could never remove the attribute in a future update.\n if (\n attr.name === \"lvt-preserve\" ||\n attr.name === \"lvt-preserve-attrs\"\n ) {\n continue;\n }\n // Use hasAttributeNS / setAttributeNS to preserve namespace\n // info (e.g. xlink:href on SVG elements). For plain HTML\n // attributes namespaceURI is null, so this degrades to the\n // same behaviour as setAttribute.\n if (!toElement.hasAttributeNS(attr.namespaceURI, attr.localName)) {\n toElement.setAttributeNS(attr.namespaceURI, attr.name, attr.value);\n }\n }\n // Fall through to normal diff path so children are still updated.\n }\n\n // Skip update entirely for focused form elements to preserve user\n // input. This also skips attribute updates (class, disabled, aria-*)\n // and the lvt-updated hook \u2014 use data-lvt-force-update to override.\n if (this.focusManager.shouldSkipUpdate(fromEl)) {\n return false;\n }\n\n // Only update if content actually changed\n if (fromEl.isEqualNode(toEl)) {\n return false;\n }\n // Execute lvt-updated lifecycle hook\n this.executeLifecycleHook(fromEl, \"lvt-updated\");\n return true;\n },\n onElUpdated: (el) => {\n // Textarea-specific: morphdom patches child text nodes but browsers\n // ignore textContent changes to \"dirty\" textareas (ones the user\n // has typed in), so we explicitly set .value. Inputs don't need\n // this \u2014 morphdom sets .value directly for input elements.\n if (el instanceof HTMLTextAreaElement) {\n el.value = el.textContent ?? \"\";\n }\n },\n onNodeAdded: (node) => {\n // Sync textarea value for newly inserted textarea elements\n if (node instanceof HTMLTextAreaElement) {\n node.value = node.textContent ?? \"\";\n }\n // Execute lvt-mounted lifecycle hook\n if (node.nodeType === Node.ELEMENT_NODE) {\n this.executeLifecycleHook(node as Element, \"lvt-mounted\");\n }\n },\n onBeforeNodeDiscarded: (node) => {\n // Execute lvt-destroyed lifecycle hook\n if (node.nodeType === Node.ELEMENT_NODE) {\n this.executeLifecycleHook(node as Element, \"lvt-destroyed\");\n }\n return true;\n },\n });\n\n // Restore focus to previously focused element\n this.focusManager.restoreFocusedElement();\n\n // Handle scroll directives (implicit trigger only)\n handleScrollDirectives(element);\n\n // Handle highlight directives (implicit trigger only)\n handleHighlightDirectives(element);\n\n // Handle animate directives (implicit trigger only)\n handleAnimateDirectives(element);\n\n // Set up DOM event triggers for lvt-fx: attributes with :on:{event}\n // Registry always lives on wrapperElement so teardown can find all entries\n setupFxDOMEventTriggers(element, this.wrapperElement || undefined);\n\n // Re-scan updated subtree for lvt-el:*:on:{event} DOM triggers\n this.eventDelegator.setupDOMEventTriggerDelegation(element);\n\n // Handle toast trigger directives (ephemeral client-side toasts)\n handleToastDirectives(element);\n\n // Initialize upload file inputs\n this.uploadHandler.initializeFileInputs(element);\n\n // Auto-wire change listeners for bound form fields\n this.changeAutoWirer.wireElements();\n\n // Handle form lifecycle if metadata is present\n if (meta) {\n this.formLifecycleManager.handleResponse(meta);\n }\n }\n\n /**\n * Handle upload_start response from server\n */\n private handleUploadStartResponse(response: UploadStartResponse): void {\n this.uploadHandler.handleUploadStartResponse(response);\n }\n\n /**\n * Execute lifecycle hook on an element\n * @param element - Element with lifecycle hook attribute\n * @param hookName - Name of the lifecycle hook attribute (e.g., 'lvt-mounted')\n */\n private executeLifecycleHook(element: Element, hookName: string): void {\n const hookValue = element.getAttribute(hookName);\n if (!hookValue) {\n return;\n }\n\n try {\n // Create a function from the hook value and execute it\n // The function has access to 'this' (the element) and 'event'\n const hookFunction = new Function(\"element\", hookValue);\n hookFunction.call(element, element);\n } catch (error) {\n this.logger.error(`Error executing ${hookName} hook:`, error);\n }\n }\n\n /**\n * Reset client state (useful for testing).\n *\n * Puts the client back into its pre-initialization state: tree state,\n * focus state, observers, change auto-wirer, form lifecycle, loading\n * indicator, form disabler, lvtId, AND isInitialized are all cleared.\n *\n * Behavioral note: `isInitialized` is set to false here. Prior to the\n * introduction of `resetSessionState()`, `reset()` left this flag sticky,\n * which was an inconsistency \u2014 a \"reset\" that didn't actually put the\n * client in a pre-init state. After calling reset(), the next payload\n * is treated as an initial render: the loading indicator will briefly\n * appear, forms are re-enabled, and `data-lvt-loading` is cleared. If\n * callers of reset() expected the prior sticky behavior, they should\n * not rely on init-only side effects firing exactly once per client\n * lifetime.\n */\n reset(): void {\n this.resetSessionState();\n }\n\n /**\n * Get current tree state (for debugging)\n */\n getTreeState(): TreeNode {\n return this.treeRenderer.getTreeState();\n }\n\n /**\n * Get the static structure if available\n */\n getStaticStructure(): string[] | null {\n return this.treeRenderer.getStaticStructure();\n }\n}\n\n// Auto-initialize when script loads (for browser use)\nif (typeof window !== \"undefined\") {\n LiveTemplateClient.autoInit();\n}\n", "var DOCUMENT_FRAGMENT_NODE = 11;\n\nfunction morphAttrs(fromNode, toNode) {\n var toNodeAttrs = toNode.attributes;\n var attr;\n var attrName;\n var attrNamespaceURI;\n var attrValue;\n var fromValue;\n\n // document-fragments dont have attributes so lets not do anything\n if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE || fromNode.nodeType === DOCUMENT_FRAGMENT_NODE) {\n return;\n }\n\n // update attributes on original DOM element\n for (var i = toNodeAttrs.length - 1; i >= 0; i--) {\n attr = toNodeAttrs[i];\n attrName = attr.name;\n attrNamespaceURI = attr.namespaceURI;\n attrValue = attr.value;\n\n if (attrNamespaceURI) {\n attrName = attr.localName || attrName;\n fromValue = fromNode.getAttributeNS(attrNamespaceURI, attrName);\n\n if (fromValue !== attrValue) {\n if (attr.prefix === 'xmlns'){\n attrName = attr.name; // It's not allowed to set an attribute with the XMLNS namespace without specifying the `xmlns` prefix\n }\n fromNode.setAttributeNS(attrNamespaceURI, attrName, attrValue);\n }\n } else {\n fromValue = fromNode.getAttribute(attrName);\n\n if (fromValue !== attrValue) {\n fromNode.setAttribute(attrName, attrValue);\n }\n }\n }\n\n // Remove any extra attributes found on the original DOM element that\n // weren't found on the target element.\n var fromNodeAttrs = fromNode.attributes;\n\n for (var d = fromNodeAttrs.length - 1; d >= 0; d--) {\n attr = fromNodeAttrs[d];\n attrName = attr.name;\n attrNamespaceURI = attr.namespaceURI;\n\n if (attrNamespaceURI) {\n attrName = attr.localName || attrName;\n\n if (!toNode.hasAttributeNS(attrNamespaceURI, attrName)) {\n fromNode.removeAttributeNS(attrNamespaceURI, attrName);\n }\n } else {\n if (!toNode.hasAttribute(attrName)) {\n fromNode.removeAttribute(attrName);\n }\n }\n }\n}\n\nvar range; // Create a range object for efficently rendering strings to elements.\nvar NS_XHTML = 'http://www.w3.org/1999/xhtml';\n\nvar doc = typeof document === 'undefined' ? undefined : document;\nvar HAS_TEMPLATE_SUPPORT = !!doc && 'content' in doc.createElement('template');\nvar HAS_RANGE_SUPPORT = !!doc && doc.createRange && 'createContextualFragment' in doc.createRange();\n\nfunction createFragmentFromTemplate(str) {\n var template = doc.createElement('template');\n template.innerHTML = str;\n return template.content.childNodes[0];\n}\n\nfunction createFragmentFromRange(str) {\n if (!range) {\n range = doc.createRange();\n range.selectNode(doc.body);\n }\n\n var fragment = range.createContextualFragment(str);\n return fragment.childNodes[0];\n}\n\nfunction createFragmentFromWrap(str) {\n var fragment = doc.createElement('body');\n fragment.innerHTML = str;\n return fragment.childNodes[0];\n}\n\n/**\n * This is about the same\n * var html = new DOMParser().parseFromString(str, 'text/html');\n * return html.body.firstChild;\n *\n * @method toElement\n * @param {String} str\n */\nfunction toElement(str) {\n str = str.trim();\n if (HAS_TEMPLATE_SUPPORT) {\n // avoid restrictions on content for things like `<tr><th>Hi</th></tr>` which\n // createContextualFragment doesn't support\n // <template> support not available in IE\n return createFragmentFromTemplate(str);\n } else if (HAS_RANGE_SUPPORT) {\n return createFragmentFromRange(str);\n }\n\n return createFragmentFromWrap(str);\n}\n\n/**\n * Returns true if two node's names are the same.\n *\n * NOTE: We don't bother checking `namespaceURI` because you will never find two HTML elements with the same\n * nodeName and different namespace URIs.\n *\n * @param {Element} a\n * @param {Element} b The target element\n * @return {boolean}\n */\nfunction compareNodeNames(fromEl, toEl) {\n var fromNodeName = fromEl.nodeName;\n var toNodeName = toEl.nodeName;\n var fromCodeStart, toCodeStart;\n\n if (fromNodeName === toNodeName) {\n return true;\n }\n\n fromCodeStart = fromNodeName.charCodeAt(0);\n toCodeStart = toNodeName.charCodeAt(0);\n\n // If the target element is a virtual DOM node or SVG node then we may\n // need to normalize the tag name before comparing. Normal HTML elements that are\n // in the \"http://www.w3.org/1999/xhtml\"\n // are converted to upper case\n if (fromCodeStart <= 90 && toCodeStart >= 97) { // from is upper and to is lower\n return fromNodeName === toNodeName.toUpperCase();\n } else if (toCodeStart <= 90 && fromCodeStart >= 97) { // to is upper and from is lower\n return toNodeName === fromNodeName.toUpperCase();\n } else {\n return false;\n }\n}\n\n/**\n * Create an element, optionally with a known namespace URI.\n *\n * @param {string} name the element name, e.g. 'div' or 'svg'\n * @param {string} [namespaceURI] the element's namespace URI, i.e. the value of\n * its `xmlns` attribute or its inferred namespace.\n *\n * @return {Element}\n */\nfunction createElementNS(name, namespaceURI) {\n return !namespaceURI || namespaceURI === NS_XHTML ?\n doc.createElement(name) :\n doc.createElementNS(namespaceURI, name);\n}\n\n/**\n * Copies the children of one DOM element to another DOM element\n */\nfunction moveChildren(fromEl, toEl) {\n var curChild = fromEl.firstChild;\n while (curChild) {\n var nextChild = curChild.nextSibling;\n toEl.appendChild(curChild);\n curChild = nextChild;\n }\n return toEl;\n}\n\nfunction syncBooleanAttrProp(fromEl, toEl, name) {\n if (fromEl[name] !== toEl[name]) {\n fromEl[name] = toEl[name];\n if (fromEl[name]) {\n fromEl.setAttribute(name, '');\n } else {\n fromEl.removeAttribute(name);\n }\n }\n}\n\nvar specialElHandlers = {\n OPTION: function(fromEl, toEl) {\n var parentNode = fromEl.parentNode;\n if (parentNode) {\n var parentName = parentNode.nodeName.toUpperCase();\n if (parentName === 'OPTGROUP') {\n parentNode = parentNode.parentNode;\n parentName = parentNode && parentNode.nodeName.toUpperCase();\n }\n if (parentName === 'SELECT' && !parentNode.hasAttribute('multiple')) {\n if (fromEl.hasAttribute('selected') && !toEl.selected) {\n // Workaround for MS Edge bug where the 'selected' attribute can only be\n // removed if set to a non-empty value:\n // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12087679/\n fromEl.setAttribute('selected', 'selected');\n fromEl.removeAttribute('selected');\n }\n // We have to reset select element's selectedIndex to -1, otherwise setting\n // fromEl.selected using the syncBooleanAttrProp below has no effect.\n // The correct selectedIndex will be set in the SELECT special handler below.\n parentNode.selectedIndex = -1;\n }\n }\n syncBooleanAttrProp(fromEl, toEl, 'selected');\n },\n /**\n * The \"value\" attribute is special for the <input> element since it sets\n * the initial value. Changing the \"value\" attribute without changing the\n * \"value\" property will have no effect since it is only used to the set the\n * initial value. Similar for the \"checked\" attribute, and \"disabled\".\n */\n INPUT: function(fromEl, toEl) {\n syncBooleanAttrProp(fromEl, toEl, 'checked');\n syncBooleanAttrProp(fromEl, toEl, 'disabled');\n\n if (fromEl.value !== toEl.value) {\n fromEl.value = toEl.value;\n }\n\n if (!toEl.hasAttribute('value')) {\n fromEl.removeAttribute('value');\n }\n },\n\n TEXTAREA: function(fromEl, toEl) {\n var newValue = toEl.value;\n if (fromEl.value !== newValue) {\n fromEl.value = newValue;\n }\n\n var firstChild = fromEl.firstChild;\n if (firstChild) {\n // Needed for IE. Apparently IE sets the placeholder as the\n // node value and vise versa. This ignores an empty update.\n var oldValue = firstChild.nodeValue;\n\n if (oldValue == newValue || (!newValue && oldValue == fromEl.placeholder)) {\n return;\n }\n\n firstChild.nodeValue = newValue;\n }\n },\n SELECT: function(fromEl, toEl) {\n if (!toEl.hasAttribute('multiple')) {\n var selectedIndex = -1;\n var i = 0;\n // We have to loop through children of fromEl, not toEl since nodes can be moved\n // from toEl to fromEl directly when morphing.\n // At the time this special handler is invoked, all children have already been morphed\n // and appended to / removed from fromEl, so using fromEl here is safe and correct.\n var curChild = fromEl.firstChild;\n var optgroup;\n var nodeName;\n while(curChild) {\n nodeName = curChild.nodeName && curChild.nodeName.toUpperCase();\n if (nodeName === 'OPTGROUP') {\n optgroup = curChild;\n curChild = optgroup.firstChild;\n // handle empty optgroups\n if (!curChild) {\n curChild = optgroup.nextSibling;\n optgroup = null;\n }\n } else {\n if (nodeName === 'OPTION') {\n if (curChild.hasAttribute('selected')) {\n selectedIndex = i;\n break;\n }\n i++;\n }\n curChild = curChild.nextSibling;\n if (!curChild && optgroup) {\n curChild = optgroup.nextSibling;\n optgroup = null;\n }\n }\n }\n\n fromEl.selectedIndex = selectedIndex;\n }\n }\n};\n\nvar ELEMENT_NODE = 1;\nvar DOCUMENT_FRAGMENT_NODE$1 = 11;\nvar TEXT_NODE = 3;\nvar COMMENT_NODE = 8;\n\nfunction noop() {}\n\nfunction defaultGetNodeKey(node) {\n if (node) {\n return (node.getAttribute && node.getAttribute('id')) || node.id;\n }\n}\n\nfunction morphdomFactory(morphAttrs) {\n\n return function morphdom(fromNode, toNode, options) {\n if (!options) {\n options = {};\n }\n\n if (typeof toNode === 'string') {\n if (fromNode.nodeName === '#document' || fromNode.nodeName === 'HTML' || fromNode.nodeName === 'BODY') {\n var toNodeHtml = toNode;\n toNode = doc.createElement('html');\n toNode.innerHTML = toNodeHtml;\n } else {\n toNode = toElement(toNode);\n }\n } else if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE$1) {\n toNode = toNode.firstElementChild;\n }\n\n var getNodeKey = options.getNodeKey || defaultGetNodeKey;\n var onBeforeNodeAdded = options.onBeforeNodeAdded || noop;\n var onNodeAdded = options.onNodeAdded || noop;\n var onBeforeElUpdated = options.onBeforeElUpdated || noop;\n var onElUpdated = options.onElUpdated || noop;\n var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop;\n var onNodeDiscarded = options.onNodeDiscarded || noop;\n var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop;\n var skipFromChildren = options.skipFromChildren || noop;\n var addChild = options.addChild || function(parent, child){ return parent.appendChild(child); };\n var childrenOnly = options.childrenOnly === true;\n\n // This object is used as a lookup to quickly find all keyed elements in the original DOM tree.\n var fromNodesLookup = Object.create(null);\n var keyedRemovalList = [];\n\n function addKeyedRemoval(key) {\n keyedRemovalList.push(key);\n }\n\n function walkDiscardedChildNodes(node, skipKeyedNodes) {\n if (node.nodeType === ELEMENT_NODE) {\n var curChild = node.firstChild;\n while (curChild) {\n\n var key = undefined;\n\n if (skipKeyedNodes && (key = getNodeKey(curChild))) {\n // If we are skipping keyed nodes then we add the key\n // to a list so that it can be handled at the very end.\n addKeyedRemoval(key);\n } else {\n // Only report the node as discarded if it is not keyed. We do this because\n // at the end we loop through all keyed elements that were unmatched\n // and then discard them in one final pass.\n onNodeDiscarded(curChild);\n if (curChild.firstChild) {\n walkDiscardedChildNodes(curChild, skipKeyedNodes);\n }\n }\n\n curChild = curChild.nextSibling;\n }\n }\n }\n\n /**\n * Removes a DOM node out of the original DOM\n *\n * @param {Node} node The node to remove\n * @param {Node} parentNode The nodes parent\n * @param {Boolean} skipKeyedNodes If true then elements with keys will be skipped and not discarded.\n * @return {undefined}\n */\n function removeNode(node, parentNode, skipKeyedNodes) {\n if (onBeforeNodeDiscarded(node) === false) {\n return;\n }\n\n if (parentNode) {\n parentNode.removeChild(node);\n }\n\n onNodeDiscarded(node);\n walkDiscardedChildNodes(node, skipKeyedNodes);\n }\n\n // // TreeWalker implementation is no faster, but keeping this around in case this changes in the future\n // function indexTree(root) {\n // var treeWalker = document.createTreeWalker(\n // root,\n // NodeFilter.SHOW_ELEMENT);\n //\n // var el;\n // while((el = treeWalker.nextNode())) {\n // var key = getNodeKey(el);\n // if (key) {\n // fromNodesLookup[key] = el;\n // }\n // }\n // }\n\n // // NodeIterator implementation is no faster, but keeping this around in case this changes in the future\n //\n // function indexTree(node) {\n // var nodeIterator = document.createNodeIterator(node, NodeFilter.SHOW_ELEMENT);\n // var el;\n // while((el = nodeIterator.nextNode())) {\n // var key = getNodeKey(el);\n // if (key) {\n // fromNodesLookup[key] = el;\n // }\n // }\n // }\n\n function indexTree(node) {\n if (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE$1) {\n var curChild = node.firstChild;\n while (curChild) {\n var key = getNodeKey(curChild);\n if (key) {\n fromNodesLookup[key] = curChild;\n }\n\n // Walk recursively\n indexTree(curChild);\n\n curChild = curChild.nextSibling;\n }\n }\n }\n\n indexTree(fromNode);\n\n function handleNodeAdded(el) {\n onNodeAdded(el);\n\n var curChild = el.firstChild;\n while (curChild) {\n var nextSibling = curChild.nextSibling;\n\n var key = getNodeKey(curChild);\n if (key) {\n var unmatchedFromEl = fromNodesLookup[key];\n // if we find a duplicate #id node in cache, replace `el` with cache value\n // and morph it to the child node.\n if (unmatchedFromEl && compareNodeNames(curChild, unmatchedFromEl)) {\n curChild.parentNode.replaceChild(unmatchedFromEl, curChild);\n morphEl(unmatchedFromEl, curChild);\n } else {\n handleNodeAdded(curChild);\n }\n } else {\n // recursively call for curChild and it's children to see if we find something in\n // fromNodesLookup\n handleNodeAdded(curChild);\n }\n\n curChild = nextSibling;\n }\n }\n\n function cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey) {\n // We have processed all of the \"to nodes\". If curFromNodeChild is\n // non-null then we still have some from nodes left over that need\n // to be removed\n while (curFromNodeChild) {\n var fromNextSibling = curFromNodeChild.nextSibling;\n if ((curFromNodeKey = getNodeKey(curFromNodeChild))) {\n // Since the node is keyed it might be matched up later so we defer\n // the actual removal to later\n addKeyedRemoval(curFromNodeKey);\n } else {\n // NOTE: we skip nested keyed nodes from being removed since there is\n // still a chance they will be matched up later\n removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);\n }\n curFromNodeChild = fromNextSibling;\n }\n }\n\n function morphEl(fromEl, toEl, childrenOnly) {\n var toElKey = getNodeKey(toEl);\n\n if (toElKey) {\n // If an element with an ID is being morphed then it will be in the final\n // DOM so clear it out of the saved elements collection\n delete fromNodesLookup[toElKey];\n }\n\n if (!childrenOnly) {\n // optional\n var beforeUpdateResult = onBeforeElUpdated(fromEl, toEl);\n if (beforeUpdateResult === false) {\n return;\n } else if (beforeUpdateResult instanceof HTMLElement) {\n fromEl = beforeUpdateResult;\n // reindex the new fromEl in case it's not in the same\n // tree as the original fromEl\n // (Phoenix LiveView sometimes returns a cloned tree,\n // but keyed lookups would still point to the original tree)\n indexTree(fromEl);\n }\n\n // update attributes on original DOM element first\n morphAttrs(fromEl, toEl);\n // optional\n onElUpdated(fromEl);\n\n if (onBeforeElChildrenUpdated(fromEl, toEl) === false) {\n return;\n }\n }\n\n if (fromEl.nodeName !== 'TEXTAREA') {\n morphChildren(fromEl, toEl);\n } else {\n specialElHandlers.TEXTAREA(fromEl, toEl);\n }\n }\n\n function morphChildren(fromEl, toEl) {\n var skipFrom = skipFromChildren(fromEl, toEl);\n var curToNodeChild = toEl.firstChild;\n var curFromNodeChild = fromEl.firstChild;\n var curToNodeKey;\n var curFromNodeKey;\n\n var fromNextSibling;\n var toNextSibling;\n var matchingFromEl;\n\n // walk the children\n outer: while (curToNodeChild) {\n toNextSibling = curToNodeChild.nextSibling;\n curToNodeKey = getNodeKey(curToNodeChild);\n\n // walk the fromNode children all the way through\n while (!skipFrom && curFromNodeChild) {\n fromNextSibling = curFromNodeChild.nextSibling;\n\n if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) {\n curToNodeChild = toNextSibling;\n curFromNodeChild = fromNextSibling;\n continue outer;\n }\n\n curFromNodeKey = getNodeKey(curFromNodeChild);\n\n var curFromNodeType = curFromNodeChild.nodeType;\n\n // this means if the curFromNodeChild doesnt have a match with the curToNodeChild\n var isCompatible = undefined;\n\n if (curFromNodeType === curToNodeChild.nodeType) {\n if (curFromNodeType === ELEMENT_NODE) {\n // Both nodes being compared are Element nodes\n\n if (curToNodeKey) {\n // The target node has a key so we want to match it up with the correct element\n // in the original DOM tree\n if (curToNodeKey !== curFromNodeKey) {\n // The current element in the original DOM tree does not have a matching key so\n // let's check our lookup to see if there is a matching element in the original\n // DOM tree\n if ((matchingFromEl = fromNodesLookup[curToNodeKey])) {\n if (fromNextSibling === matchingFromEl) {\n // Special case for single element removals. To avoid removing the original\n // DOM node out of the tree (since that can break CSS transitions, etc.),\n // we will instead discard the current node and wait until the next\n // iteration to properly match up the keyed target element with its matching\n // element in the original tree\n isCompatible = false;\n } else {\n // We found a matching keyed element somewhere in the original DOM tree.\n // Let's move the original DOM node into the current position and morph\n // it.\n\n // NOTE: We use insertBefore instead of replaceChild because we want to go through\n // the `removeNode()` function for the node that is being discarded so that\n // all lifecycle hooks are correctly invoked\n fromEl.insertBefore(matchingFromEl, curFromNodeChild);\n\n // fromNextSibling = curFromNodeChild.nextSibling;\n\n if (curFromNodeKey) {\n // Since the node is keyed it might be matched up later so we defer\n // the actual removal to later\n addKeyedRemoval(curFromNodeKey);\n } else {\n // NOTE: we skip nested keyed nodes from being removed since there is\n // still a chance they will be matched up later\n removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);\n }\n\n curFromNodeChild = matchingFromEl;\n curFromNodeKey = getNodeKey(curFromNodeChild);\n }\n } else {\n // The nodes are not compatible since the \"to\" node has a key and there\n // is no matching keyed node in the source tree\n isCompatible = false;\n }\n }\n } else if (curFromNodeKey) {\n // The original has a key\n isCompatible = false;\n }\n\n isCompatible = isCompatible !== false && compareNodeNames(curFromNodeChild, curToNodeChild);\n if (isCompatible) {\n // We found compatible DOM elements so transform\n // the current \"from\" node to match the current\n // target DOM node.\n // MORPH\n morphEl(curFromNodeChild, curToNodeChild);\n }\n\n } else if (curFromNodeType === TEXT_NODE || curFromNodeType == COMMENT_NODE) {\n // Both nodes being compared are Text or Comment nodes\n isCompatible = true;\n // Simply update nodeValue on the original node to\n // change the text value\n if (curFromNodeChild.nodeValue !== curToNodeChild.nodeValue) {\n curFromNodeChild.nodeValue = curToNodeChild.nodeValue;\n }\n\n }\n }\n\n if (isCompatible) {\n // Advance both the \"to\" child and the \"from\" child since we found a match\n // Nothing else to do as we already recursively called morphChildren above\n curToNodeChild = toNextSibling;\n curFromNodeChild = fromNextSibling;\n continue outer;\n }\n\n // No compatible match so remove the old node from the DOM and continue trying to find a\n // match in the original DOM. However, we only do this if the from node is not keyed\n // since it is possible that a keyed node might match up with a node somewhere else in the\n // target tree and we don't want to discard it just yet since it still might find a\n // home in the final DOM tree. After everything is done we will remove any keyed nodes\n // that didn't find a home\n if (curFromNodeKey) {\n // Since the node is keyed it might be matched up later so we defer\n // the actual removal to later\n addKeyedRemoval(curFromNodeKey);\n } else {\n // NOTE: we skip nested keyed nodes from being removed since there is\n // still a chance they will be matched up later\n removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);\n }\n\n curFromNodeChild = fromNextSibling;\n } // END: while(curFromNodeChild) {}\n\n // If we got this far then we did not find a candidate match for\n // our \"to node\" and we exhausted all of the children \"from\"\n // nodes. Therefore, we will just append the current \"to\" node\n // to the end\n if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) {\n // MORPH\n if(!skipFrom){ addChild(fromEl, matchingFromEl); }\n morphEl(matchingFromEl, curToNodeChild);\n } else {\n var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild);\n if (onBeforeNodeAddedResult !== false) {\n if (onBeforeNodeAddedResult) {\n curToNodeChild = onBeforeNodeAddedResult;\n }\n\n if (curToNodeChild.actualize) {\n curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc);\n }\n addChild(fromEl, curToNodeChild);\n handleNodeAdded(curToNodeChild);\n }\n }\n\n curToNodeChild = toNextSibling;\n curFromNodeChild = fromNextSibling;\n }\n\n cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey);\n\n var specialElHandler = specialElHandlers[fromEl.nodeName];\n if (specialElHandler) {\n specialElHandler(fromEl, toEl);\n }\n } // END: morphChildren(...)\n\n var morphedNode = fromNode;\n var morphedNodeType = morphedNode.nodeType;\n var toNodeType = toNode.nodeType;\n\n if (!childrenOnly) {\n // Handle the case where we are given two DOM nodes that are not\n // compatible (e.g. <div> --> <span> or <div> --> TEXT)\n if (morphedNodeType === ELEMENT_NODE) {\n if (toNodeType === ELEMENT_NODE) {\n if (!compareNodeNames(fromNode, toNode)) {\n onNodeDiscarded(fromNode);\n morphedNode = moveChildren(fromNode, createElementNS(toNode.nodeName, toNode.namespaceURI));\n }\n } else {\n // Going from an element node to a text node\n morphedNode = toNode;\n }\n } else if (morphedNodeType === TEXT_NODE || morphedNodeType === COMMENT_NODE) { // Text or comment node\n if (toNodeType === morphedNodeType) {\n if (morphedNode.nodeValue !== toNode.nodeValue) {\n morphedNode.nodeValue = toNode.nodeValue;\n }\n\n return morphedNode;\n } else {\n // Text node to something else\n morphedNode = toNode;\n }\n }\n }\n\n if (morphedNode === toNode) {\n // The \"to node\" was not compatible with the \"from node\" so we had to\n // toss out the \"from node\" and use the \"to node\"\n onNodeDiscarded(fromNode);\n } else {\n if (toNode.isSameNode && toNode.isSameNode(morphedNode)) {\n return;\n }\n\n morphEl(morphedNode, toNode, childrenOnly);\n\n // We now need to loop over any keyed nodes that might need to be\n // removed. We only do the removal if we know that the keyed node\n // never found a match. When a keyed node is matched up we remove\n // it out of fromNodesLookup and we use fromNodesLookup to determine\n // if a keyed node has been matched up or not\n if (keyedRemovalList) {\n for (var i=0, len=keyedRemovalList.length; i<len; i++) {\n var elToRemove = fromNodesLookup[keyedRemovalList[i]];\n if (elToRemove) {\n removeNode(elToRemove, elToRemove.parentNode, false);\n }\n }\n }\n }\n\n if (!childrenOnly && morphedNode !== fromNode && fromNode.parentNode) {\n if (morphedNode.actualize) {\n morphedNode = morphedNode.actualize(fromNode.ownerDocument || doc);\n }\n // If we had to swap out the from node with a new node because the old\n // node was not compatible with the target node then we need to\n // replace the old DOM node in the original DOM tree. This is only\n // possible if the original DOM node was part of a DOM tree which\n // we know is the case if it has a parent node.\n fromNode.parentNode.replaceChild(morphedNode, fromNode);\n }\n\n return morphedNode;\n };\n}\n\nvar morphdom = morphdomFactory(morphAttrs);\n\nexport default morphdom;\n", "export const DEFAULT_CHANGE_DEBOUNCE_MS = 300;\n\nexport const FOCUSABLE_INPUTS = [\n \"text\",\n \"textarea\",\n \"number\",\n \"email\",\n \"password\",\n \"search\",\n \"tel\",\n \"url\",\n \"date\",\n \"time\",\n \"datetime-local\",\n \"color\",\n \"range\",\n];\n", "import { FOCUSABLE_INPUTS } from \"../constants\";\nimport type { Logger } from \"../utils/logger\";\n\nexport class FocusManager {\n private wrapperElement: Element | null = null;\n private focusableElements: HTMLElement[] = [];\n private lastFocusedElement: HTMLElement | null = null;\n private lastFocusedSelectionStart: number | null = null;\n private lastFocusedSelectionEnd: number | null = null;\n\n constructor(private readonly logger: Logger) {}\n\n attach(wrapper: Element | null): void {\n this.wrapperElement = wrapper;\n\n if (!wrapper) {\n return;\n }\n\n this.updateFocusableElements();\n this.setupFocusTracking();\n }\n\n reset(): void {\n this.wrapperElement = null;\n this.focusableElements = [];\n this.lastFocusedElement = null;\n this.lastFocusedSelectionStart = null;\n this.lastFocusedSelectionEnd = null;\n }\n\n updateFocusableElements(): void {\n if (!this.wrapperElement) return;\n\n const inputSelectors = FOCUSABLE_INPUTS.map((type) =>\n type === \"textarea\"\n ? \"textarea:not([disabled])\"\n : `input[type=\"${type}\"]:not([disabled])`\n ).join(\", \");\n\n const otherFocusable =\n 'select:not([disabled]), button:not([disabled]), [contenteditable=\"true\"], [tabindex]:not([tabindex=\"-1\"])';\n const selector = `${inputSelectors}, ${otherFocusable}`;\n\n this.focusableElements = Array.from(\n this.wrapperElement.querySelectorAll(selector)\n );\n }\n\n setupFocusTracking(): void {\n if (!this.wrapperElement) return;\n\n const wrapperId = this.wrapperElement.getAttribute(\"data-lvt-id\");\n const focusKey = `__lvt_focus_tracker_${wrapperId}`;\n const blurKey = `__lvt_blur_tracker_${wrapperId}`;\n\n const focusListener = (event: Event) => {\n const target = event.target as HTMLElement;\n if (!target || !this.wrapperElement?.contains(target)) return;\n\n if (this.isTextualInput(target) || target instanceof HTMLSelectElement) {\n this.lastFocusedElement = target;\n this.logger.debug(\n \"[Focus] Tracked focus on:\",\n target.tagName,\n target.id || target.getAttribute(\"name\")\n );\n\n if (this.isTextualInput(target)) {\n this.lastFocusedSelectionStart = target.selectionStart;\n this.lastFocusedSelectionEnd = target.selectionEnd;\n }\n }\n };\n\n const blurListener = (event: Event) => {\n const target = event.target as HTMLElement;\n if (!target || !this.wrapperElement?.contains(target)) return;\n\n if (this.isTextualInput(target) && target === this.lastFocusedElement) {\n this.lastFocusedSelectionStart = target.selectionStart;\n this.lastFocusedSelectionEnd = target.selectionEnd;\n this.logger.debug(\n \"[Focus] Saved cursor on blur:\",\n this.lastFocusedSelectionStart,\n \"-\",\n this.lastFocusedSelectionEnd\n );\n }\n };\n\n if ((document as any)[focusKey]) {\n document.removeEventListener(\"focus\", (document as any)[focusKey], true);\n }\n if ((document as any)[blurKey]) {\n document.removeEventListener(\"blur\", (document as any)[blurKey], true);\n }\n\n (document as any)[focusKey] = focusListener;\n (document as any)[blurKey] = blurListener;\n\n document.addEventListener(\"focus\", focusListener, true);\n document.addEventListener(\"blur\", blurListener, true);\n\n this.logger.debug(\"[Focus] Focus tracking set up\");\n }\n\n restoreFocusedElement(): void {\n this.logger.debug(\n \"[Focus] restoreFocusedElement - lastFocusedElement:\",\n this.lastFocusedElement?.tagName,\n this.lastFocusedElement?.id ||\n this.lastFocusedElement?.getAttribute(\"name\")\n );\n\n if (!this.lastFocusedElement || !this.wrapperElement) {\n this.logger.debug(\"[Focus] No element to restore\");\n return;\n }\n\n const selector = this.getElementSelector(this.lastFocusedElement);\n this.logger.debug(\"[Focus] Selector for last focused:\", selector);\n\n if (!selector) {\n this.logger.debug(\"[Focus] Could not generate selector\");\n return;\n }\n\n let element: HTMLElement | null = null;\n\n if (selector.startsWith(\"data-focus-index-\")) {\n this.updateFocusableElements();\n const index = parseInt(selector.replace(\"data-focus-index-\", \"\"), 10);\n element = this.focusableElements[index] || null;\n this.logger.debug(\"[Focus] Found by index:\", index, element?.tagName);\n } else {\n element = this.wrapperElement.querySelector(selector);\n this.logger.debug(\n \"[Focus] Found by selector:\",\n selector,\n element?.tagName\n );\n }\n\n if (!element) {\n this.logger.debug(\"[Focus] Element not found in updated DOM\");\n return;\n }\n\n const wasFocused = element.matches(\":focus\");\n this.logger.debug(\"[Focus] Already focused:\", wasFocused);\n\n if (wasFocused) {\n this.logger.debug(\n \"[Focus] Element retained focus \u2014 skipping restoration\"\n );\n return;\n }\n\n // Capture selection before focus() \u2014 the focus event listener fires\n // synchronously and overwrites lastFocusedSelection* with the new\n // element's current (wrong) cursor position.\n const selectionStart = this.lastFocusedSelectionStart;\n const selectionEnd = this.lastFocusedSelectionEnd;\n\n element.focus();\n this.logger.debug(\"[Focus] Restored focus\");\n\n if (\n this.isTextualInput(element) &&\n selectionStart !== null &&\n selectionEnd !== null\n ) {\n element.setSelectionRange(selectionStart, selectionEnd);\n this.logger.debug(\n \"[Focus] Restored cursor:\",\n selectionStart,\n \"-\",\n selectionEnd\n );\n }\n }\n\n isTextualInput(el: Element): el is HTMLInputElement | HTMLTextAreaElement {\n if (el instanceof HTMLTextAreaElement) return true;\n if (el instanceof HTMLInputElement) {\n return FOCUSABLE_INPUTS.indexOf(el.type) >= 0;\n }\n return false;\n }\n\n shouldSkipUpdate(el: Element): boolean {\n if (el !== document.activeElement) {\n return false;\n }\n\n if (el.hasAttribute(\"data-lvt-force-update\")) {\n return false;\n }\n\n if (this.isTextualInput(el)) {\n return true;\n }\n\n if (el instanceof HTMLSelectElement) {\n return true;\n }\n\n return false;\n }\n\n getLastFocusedElement(): HTMLElement | null {\n return this.lastFocusedElement;\n }\n\n private getElementSelector(el: HTMLElement): string | null {\n if (el.id) return `#${el.id}`;\n if ((el as any).name) return `[name=\"${(el as any).name}\"]`;\n if (el.getAttribute(\"data-key\"))\n return `[data-key=\"${el.getAttribute(\"data-key\")}\"]`;\n\n const index = this.focusableElements.indexOf(el);\n return index >= 0 ? `data-focus-index-${index}` : null;\n }\n}\n", "/**\n * Reactive Attributes - Declarative DOM actions triggered by lifecycle events or interactions.\n *\n * Attribute Pattern: lvt-el:{method}:on:{trigger}=\"param\"\n *\n * Trigger types:\n *\n * 1. Lifecycle states (server action request-response cycle):\n * - pending, success, error, done\n * - Supports action scoping: lvt-el:reset:on:create-todo:success\n *\n * 2. Native DOM events (client-side, no server round-trip):\n * - Any browser event: click, focusin, focusout, mouseenter, mouseleave, keydown, etc.\n * - No action scoping (fires on the element's own event)\n *\n * 3. Synthetic interactions (client-side):\n * - click-away: Click outside the element\n * - No action scoping\n *\n * Methods:\n * - reset: Calls form.reset()\n * - addClass: Adds CSS class(es)\n * - removeClass: Removes CSS class(es)\n * - toggleClass: Toggles CSS class(es)\n * - setAttr: Sets an attribute (name:value format)\n * - toggleAttr: Toggles a boolean attribute\n */\n\nexport type ReactiveAction =\n | \"reset\"\n | \"addClass\"\n | \"removeClass\"\n | \"toggleClass\"\n | \"setAttr\"\n | \"toggleAttr\";\n\nexport type LifecycleEvent = \"pending\" | \"success\" | \"error\" | \"done\";\n\nexport interface ReactiveBinding {\n action: ReactiveAction;\n lifecycle: LifecycleEvent;\n actionName?: string;\n param?: string;\n}\n\nconst LIFECYCLE_EVENTS: LifecycleEvent[] = [\"pending\", \"success\", \"error\", \"done\"];\nconst LIFECYCLE_SET = new Set<string>(LIFECYCLE_EVENTS);\n\n/**\n * Reserved trigger keywords that are NOT native DOM events.\n * click-away is a synthetic interaction handled by setupClickAwayDelegation.\n * Everything else that's not a lifecycle state is treated as a native DOM event.\n */\nexport const SYNTHETIC_TRIGGERS = new Set([\"click-away\"]);\n\n// Lowercase method names \u2192 canonical ReactiveAction\nconst METHOD_MAP: Record<string, ReactiveAction> = {\n reset: \"reset\",\n addclass: \"addClass\",\n removeclass: \"removeClass\",\n toggleclass: \"toggleClass\",\n setattr: \"setAttr\",\n toggleattr: \"toggleAttr\",\n};\n\n/**\n * Parse a reactive attribute name and value into a binding.\n *\n * Supported pattern: lvt-el:{method}:on:[{action}:]{state}\n *\n * Examples:\n * parseReactiveAttribute(\"lvt-el:reset:on:success\", \"\") => { action: \"reset\", lifecycle: \"success\" }\n * parseReactiveAttribute(\"lvt-el:addclass:on:pending\", \"loading\") => { action: \"addClass\", lifecycle: \"pending\", param: \"loading\" }\n * parseReactiveAttribute(\"lvt-el:reset:on:create-todo:success\", \"\") => { action: \"reset\", lifecycle: \"success\", actionName: \"create-todo\" }\n */\nexport function parseReactiveAttribute(\n attrName: string,\n attrValue: string\n): ReactiveBinding | null {\n const lower = attrName.toLowerCase();\n\n // New pattern: lvt-el:{method}:on:[{action}:]{state}\n const newMatch = lower.match(/^lvt-el:(\\w+):on:(.+)$/);\n if (newMatch) {\n const methodKey = newMatch[1];\n const action = METHOD_MAP[methodKey];\n if (!action) return null;\n\n const eventPart = newMatch[2];\n // Skip synthetic triggers (click-away) \u2014 handled by setupClickAwayDelegation\n if (SYNTHETIC_TRIGGERS.has(eventPart)) return null;\n // Skip native DOM event triggers \u2014 handled by setupDOMEventTriggerDelegation\n if (!LIFECYCLE_SET.has(eventPart) && !eventPart.includes(\":\")) return null;\n\n const segments = eventPart.split(\":\");\n const lastSegment = segments[segments.length - 1];\n if (!LIFECYCLE_SET.has(lastSegment)) return null;\n\n const lifecycle = lastSegment as LifecycleEvent;\n const actionName = segments.length > 1 ? segments.slice(0, -1).join(\":\") : undefined;\n\n return {\n action,\n lifecycle,\n actionName: actionName || undefined,\n param: attrValue || undefined,\n };\n }\n\n return null;\n}\n\n/**\n * Resolve the target element for an lvt-el: action.\n * If the element has data-lvt-target, resolves to the specified element:\n * - \"#id\" \u2192 document.getElementById(id)\n * - \"closest:sel\" \u2192 element.closest(sel)\n * Falls back to the element itself if no target or target not found.\n */\nexport function resolveTarget(element: Element): Element {\n const selector = element.getAttribute(\"data-lvt-target\");\n if (!selector) return element;\n if (selector.startsWith(\"#\")) {\n return document.getElementById(selector.slice(1)) || element;\n }\n if (selector.startsWith(\"closest:\")) {\n return element.closest(selector.slice(8)) || element;\n }\n return element;\n}\n\n/**\n * Execute a reactive action on an element.\n */\nexport function executeAction(\n element: Element,\n action: ReactiveAction,\n param?: string\n): void {\n switch (action) {\n case \"reset\":\n if (element instanceof HTMLFormElement) {\n element.reset();\n }\n break;\n\n case \"addClass\":\n if (param) {\n const classes = param.split(/\\s+/).filter(Boolean);\n element.classList.add(...classes);\n }\n break;\n\n case \"removeClass\":\n if (param) {\n const classes = param.split(/\\s+/).filter(Boolean);\n element.classList.remove(...classes);\n }\n break;\n\n case \"toggleClass\":\n if (param) {\n const classes = param.split(/\\s+/).filter(Boolean);\n classes.forEach((c) => element.classList.toggle(c));\n }\n break;\n\n case \"setAttr\":\n if (param) {\n const colonIndex = param.indexOf(\":\");\n if (colonIndex > 0) {\n const name = param.substring(0, colonIndex);\n const value = param.substring(colonIndex + 1);\n element.setAttribute(name, value);\n }\n }\n break;\n\n case \"toggleAttr\":\n if (param) {\n element.toggleAttribute(param);\n }\n break;\n }\n}\n\n/**\n * Check if an event matches a binding.\n */\nexport function matchesEvent(\n binding: ReactiveBinding,\n lifecycle: LifecycleEvent,\n actionName?: string\n): boolean {\n if (binding.lifecycle !== lifecycle) return false;\n if (!binding.actionName) return true;\n return binding.actionName === actionName;\n}\n\n/**\n * Process all reactive attributes for a lifecycle event.\n */\nexport function processReactiveAttributes(\n lifecycle: LifecycleEvent,\n actionName?: string\n): void {\n // Target only elements with lvt-el: attributes instead of scanning all DOM elements.\n // CSS doesn't support attribute-name-starts-with, so we build selectors from known\n // method prefixes. This covers both unscoped (lvt-el:reset:on:success) and\n // action-scoped (lvt-el:reset:on:create-todo:success) patterns.\n const methodKeys = Object.keys(METHOD_MAP);\n const selectorParts: string[] = [];\n\n // Escape CSS-special characters in actionName for use in attribute selectors\n const escapedAction = actionName\n ? actionName.replace(/([^\\w-])/g, \"\\\\$1\")\n : undefined;\n\n for (const m of methodKeys) {\n selectorParts.push(`[lvt-el\\\\:${m}\\\\:on\\\\:${lifecycle}]`);\n if (escapedAction) {\n selectorParts.push(`[lvt-el\\\\:${m}\\\\:on\\\\:${escapedAction}\\\\:${lifecycle}]`);\n }\n }\n const selector = selectorParts.join(\", \");\n\n let candidates: NodeListOf<Element>;\n try {\n candidates = document.querySelectorAll(selector);\n } catch {\n // If selector is still invalid despite escaping, scan targeted elements only\n // by matching unscoped patterns (without actionName)\n const fallbackParts = methodKeys.map(m => `[lvt-el\\\\:${m}\\\\:on\\\\:${lifecycle}]`);\n try {\n candidates = document.querySelectorAll(fallbackParts.join(\", \"));\n } catch {\n return; // Cannot construct any valid selector\n }\n }\n\n candidates.forEach((element) => {\n Array.from(element.attributes).forEach((attr) => {\n if (!attr.name.startsWith(\"lvt-el:\") || !attr.name.includes(\":on:\")) {\n return;\n }\n\n const binding = parseReactiveAttribute(attr.name, attr.value);\n if (binding && matchesEvent(binding, lifecycle, actionName)) {\n executeAction(resolveTarget(element), binding.action, binding.param);\n }\n });\n });\n}\n\n/**\n * Process all lvt-el:*:on:{trigger} attributes on an element for a given trigger.\n */\nexport function processElementInteraction(element: Element, trigger: string): void {\n for (const attr of element.attributes) {\n const match = attr.name.match(/^lvt-el:(\\w+):on:([a-z-]+)$/i);\n if (!match) continue;\n if (match[2].toLowerCase() !== trigger) continue;\n\n const methodKey = match[1].toLowerCase();\n const action = METHOD_MAP[methodKey];\n if (!action) continue;\n\n executeAction(resolveTarget(element), action, attr.value);\n }\n}\n\n/**\n * Checks if a trigger name is a DOM event (not lifecycle or synthetic).\n * Intentionally open \u2014 accepts any string to support both native DOM events\n * and custom events (e.g., lvt-el:addClass:on:my-custom-event). A typo\n * silently registers a listener that never fires; no allowlist is enforced.\n */\nexport function isDOMEventTrigger(trigger: string): boolean {\n return !LIFECYCLE_SET.has(trigger) && !SYNTHETIC_TRIGGERS.has(trigger);\n}\n\n/**\n * Set up document-level event listeners for reactive attributes.\n */\nexport function setupReactiveAttributeListeners(): void {\n LIFECYCLE_EVENTS.forEach((lifecycle) => {\n document.addEventListener(\n `lvt:${lifecycle}`,\n (e: Event) => {\n const customEvent = e as CustomEvent;\n const actionName = customEvent.detail?.action;\n processReactiveAttributes(lifecycle, actionName);\n },\n true\n );\n });\n}\n", "import { isDOMEventTrigger, SYNTHETIC_TRIGGERS } from \"./reactive-attributes\";\n\n// \u2500\u2500\u2500 Trigger parsing for lvt-fx: attributes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst FX_LIFECYCLE_SET = new Set([\"pending\", \"success\", \"error\", \"done\"]);\n\n// Tracks elements whose entry animation has already played. Kept as a\n// module-level WeakSet (rather than stashed on the DOM node) so it's\n// type-safe and automatically cleaned up when elements are GC'd.\n//\n// Semantic: once per element lifetime. An element added to this set will\n// NEVER animate again, even if the same node is updated in place. This is\n// intentional \u2014 lvt-fx:animate is an entry animation, not a per-update\n// flash. Morphdom creates fresh DOM nodes for newly-inserted range items\n// (which are not in the set, so they animate) while reusing nodes for\n// in-place updates (already in the set, so they skip). Use cases that\n// want a visible pulse on every update should reach for lvt-fx:highlight.\nlet animatedElements = new WeakSet<Element>();\n\n/**\n * Test-only: reset the module-level animatedElements WeakSet. Required\n * for tests that reuse the same DOM nodes across cases \u2014 without this,\n * an element animated in case 1 would be silently skipped in case 2.\n * Production code should never call this.\n *\n * The double-underscore prefix and the `@internal` tag signal that this\n * is not part of the public API. The `@internal` tag is only enforced\n * when TypeScript's API Extractor (or equivalent) is configured to\n * strip it from generated `.d.ts` files \u2014 this project does not\n * currently run API Extractor, so the tag is aspirational enforcement\n * backed by the `__` naming convention and this docstring.\n *\n * @internal\n */\nexport function __resetAnimatedElementsForTesting(): void {\n animatedElements = new WeakSet<Element>();\n}\n\n/**\n * Parse a lvt-fx:{effect}[:on:[{action}:]{trigger}] attribute name.\n * Returns the trigger type or null for implicit (no :on:).\n */\nfunction parseFxTrigger(attrName: string): { trigger: string | null; actionName?: string } {\n // Check for :on: suffix pattern\n const onMatch = attrName.match(/^lvt-fx:\\w+:on:(.+)$/i);\n if (!onMatch) return { trigger: null }; // implicit trigger\n\n const parts = onMatch[1].split(\":\");\n if (parts.length === 1) {\n return { trigger: parts[0].toLowerCase() };\n }\n // action-scoped: lvt-fx:highlight:on:save:success\n return {\n trigger: parts[parts.length - 1].toLowerCase(),\n actionName: parts.slice(0, -1).join(\":\"),\n };\n}\n\n/**\n * Set up DOM event listeners for lvt-fx: attributes with :on:{event} triggers.\n * Called after each DOM update to handle new elements.\n *\n * @param scanRoot - Element subtree to scan for new fx attributes.\n * @param registryRoot - Element to store listener registry on (always the wrapper).\n * Defaults to scanRoot for backwards compatibility.\n */\nexport function setupFxDOMEventTriggers(scanRoot: Element, registryRoot?: Element): void {\n const registry = registryRoot || scanRoot;\n const fxListenersKey = \"__lvtFxDirectListeners\";\n // Prune stale entries from elements replaced by morphdom\n const fxListeners: Array<{ el: Element; event: string; handler: EventListener; guardKey: string }> =\n ((registry as any)[fxListenersKey] || []).filter(\n (entry: { el: Element }) => entry.el.isConnected\n );\n\n const processEl = (el: Element) => {\n for (const attr of el.attributes) {\n if (!attr.name.startsWith(\"lvt-fx:\")) continue;\n const parsed = parseFxTrigger(attr.name);\n if (!parsed.trigger) continue; // implicit \u2014 handled by normal directive flow\n if (FX_LIFECYCLE_SET.has(parsed.trigger)) continue; // lifecycle \u2014 handled by event listeners\n if (SYNTHETIC_TRIGGERS.has(parsed.trigger)) continue; // click-away etc.\n\n // It's a DOM event trigger\n const listenerKey = `__lvt_fx_${attr.name}`;\n if ((el as any)[listenerKey]) continue; // already attached\n\n const effect = attr.name.match(/^lvt-fx:(\\w+)/i)?.[1];\n if (!effect) continue;\n\n const attrNameCapture = attr.name;\n const listener = () => {\n if (!el.hasAttribute(attrNameCapture)) return; // attr removed by morphdom\n const currentValue = el.getAttribute(attrNameCapture) || \"\";\n applyFxEffect(el as HTMLElement, effect, currentValue);\n };\n el.addEventListener(parsed.trigger, listener);\n (el as any)[listenerKey] = listener;\n fxListeners.push({ el, event: parsed.trigger, handler: listener, guardKey: listenerKey });\n }\n };\n\n // Process scan root element itself then descendants (avoids spreading NodeList)\n processEl(scanRoot);\n scanRoot.querySelectorAll(\"*\").forEach(processEl);\n\n (registry as any)[fxListenersKey] = fxListeners;\n}\n\n/**\n * Remove direct DOM event listeners registered by setupFxDOMEventTriggers.\n * Call on disconnect to prevent stale listeners across reconnects.\n */\nexport function teardownFxDOMEventTriggers(rootElement: Element): void {\n const fxListenersKey = \"__lvtFxDirectListeners\";\n const listeners: Array<{ el: Element; event: string; handler: EventListener; guardKey: string }> | undefined =\n (rootElement as any)[fxListenersKey];\n if (listeners) {\n listeners.forEach(({ el, event, handler, guardKey }) => {\n el.removeEventListener(event, handler);\n delete (el as any)[guardKey]; // Clear per-element marker so re-attach works on reconnect\n });\n delete (rootElement as any)[fxListenersKey];\n }\n}\n\n/**\n * Process lvt-fx: attributes triggered by a lifecycle event.\n */\nexport function processFxLifecycleAttributes(\n rootElement: Element,\n lifecycle: string,\n actionName?: string,\n): void {\n const processEl = (el: Element) => {\n for (const attr of el.attributes) {\n if (!attr.name.startsWith(\"lvt-fx:\")) continue;\n const parsed = parseFxTrigger(attr.name);\n if (!parsed.trigger || !FX_LIFECYCLE_SET.has(parsed.trigger)) continue;\n if (parsed.trigger !== lifecycle) continue;\n if (parsed.actionName && parsed.actionName !== actionName) continue;\n\n const effect = attr.name.match(/^lvt-fx:(\\w+)/i)?.[1];\n if (!effect) continue;\n\n applyFxEffect(el as HTMLElement, effect, attr.value);\n }\n };\n processEl(rootElement);\n rootElement.querySelectorAll(\"*\").forEach(processEl);\n}\n\n/**\n * Apply a visual effect to an element.\n */\nfunction applyFxEffect(htmlElement: HTMLElement, effect: string, config: string): void {\n const computed = getComputedStyle(htmlElement);\n\n switch (effect) {\n case \"highlight\": {\n // Skip if already mid-highlight to prevent stale originalBackground capture.\n // Intentionally rate-limits to one highlight per element \u2014 overlapping triggers\n // (rapid clicks, DOM updates during animation) are coalesced rather than stacked.\n if ((htmlElement as any).__lvtHighlighting) break;\n (htmlElement as any).__lvtHighlighting = true;\n\n const duration = parseInt(\n computed.getPropertyValue(\"--lvt-highlight-duration\").trim() || \"500\", 10\n );\n const color = computed.getPropertyValue(\"--lvt-highlight-color\").trim() || \"#ffc107\";\n const originalBackground = htmlElement.style.backgroundColor;\n const originalTransition = htmlElement.style.transition;\n\n htmlElement.style.transition = `background-color ${duration}ms ease-out`;\n htmlElement.style.backgroundColor = color;\n\n setTimeout(() => {\n if (!htmlElement.isConnected) {\n htmlElement.style.backgroundColor = originalBackground;\n htmlElement.style.transition = originalTransition;\n (htmlElement as any).__lvtHighlighting = false;\n return;\n }\n htmlElement.style.backgroundColor = originalBackground;\n setTimeout(() => {\n if (htmlElement.isConnected) htmlElement.style.transition = originalTransition;\n (htmlElement as any).__lvtHighlighting = false;\n }, duration);\n }, 50);\n break;\n }\n case \"animate\": {\n // \"Entry animation\" semantics: play once per element lifetime. Every\n // tree update re-walks lvt-fx:* attributes, so without this guard an\n // unchanged row re-fires the animation on every patch. Morphdom\n // creates fresh DOM nodes for new rows (not in the WeakSet \u2192 animate);\n // reused nodes are already in the set and skip.\n if (animatedElements.has(htmlElement)) break;\n animatedElements.add(htmlElement);\n\n const duration = parseInt(\n computed.getPropertyValue(\"--lvt-animate-duration\").trim() || \"500\", 10\n );\n const animation = config || \"fade\";\n\n let animationValue = \"\";\n switch (animation) {\n case \"fade\":\n animationValue = `lvt-fade-in ${duration}ms ease-out`;\n break;\n case \"slide\":\n animationValue = `lvt-slide-in ${duration}ms ease-out`;\n break;\n case \"scale\":\n animationValue = `lvt-scale-in ${duration}ms ease-out`;\n break;\n default:\n console.warn(`Unknown lvt-fx:animate mode: ${animation}`);\n }\n if (!animationValue) break;\n htmlElement.style.animation = animationValue;\n htmlElement.addEventListener(\"animationend\", () => {\n // Only remove the animation we set. Do NOT remove\n // --lvt-animate-duration: users may have set it inline themselves\n // (e.g. style=\"--lvt-animate-duration: 800\") to override duration,\n // and removing would wipe their intent. Clean up the style\n // attribute entirely only if nothing is left on it.\n htmlElement.style.removeProperty(\"animation\");\n if (htmlElement.style.length === 0) {\n htmlElement.removeAttribute(\"style\");\n }\n }, { once: true });\n break;\n }\n case \"scroll\": {\n const rawBehavior = computed.getPropertyValue(\"--lvt-scroll-behavior\").trim();\n const behavior: ScrollBehavior = VALID_SCROLL_BEHAVIORS.has(rawBehavior)\n ? (rawBehavior as ScrollBehavior) : \"auto\";\n const threshold = parseInt(\n computed.getPropertyValue(\"--lvt-scroll-threshold\").trim() || \"100\", 10\n );\n const mode = config || \"bottom\";\n\n switch (mode) {\n case \"bottom\":\n htmlElement.scrollTo({ top: htmlElement.scrollHeight, behavior });\n break;\n case \"bottom-sticky\": {\n const isNearBottom = htmlElement.scrollHeight - htmlElement.scrollTop - htmlElement.clientHeight <= threshold;\n if (isNearBottom) htmlElement.scrollTo({ top: htmlElement.scrollHeight, behavior });\n break;\n }\n case \"top\":\n htmlElement.scrollTo({ top: 0, behavior });\n break;\n case \"preserve\":\n break;\n default:\n console.warn(`Unknown lvt-fx:scroll mode: ${mode}`);\n }\n break;\n }\n default:\n console.warn(`Unknown lvt-fx effect: ${effect}`);\n }\n}\n\n/**\n * Set up document-level lifecycle listeners for lvt-fx: attributes with :on:{lifecycle}.\n * Called once per wrapper at connect time. Scoped to the provided root element so\n * multiple LiveTemplateClient instances on the same page don't cross-fire effects.\n * Stores listener references on the element for teardown via teardownFxLifecycleListeners.\n */\nexport function setupFxLifecycleListeners(rootElement: Element): void {\n const guardKey = \"__lvtFxLifecycleSetup\";\n if ((rootElement as any)[guardKey]) return;\n (rootElement as any)[guardKey] = true;\n\n const listeners: Array<{ event: string; handler: EventListener }> = [];\n const lifecycles = [\"pending\", \"success\", \"error\", \"done\"];\n lifecycles.forEach(lifecycle => {\n const handler = (e: Event) => {\n const customEvent = e as CustomEvent;\n const actionName = customEvent.detail?.action;\n processFxLifecycleAttributes(rootElement, lifecycle, actionName);\n };\n document.addEventListener(`lvt:${lifecycle}`, handler, true);\n listeners.push({ event: `lvt:${lifecycle}`, handler });\n });\n (rootElement as any).__lvtFxLifecycleListeners = listeners;\n}\n\n/**\n * Remove document-level lifecycle listeners registered by setupFxLifecycleListeners.\n * Call on disconnect to prevent listener accumulation across reconnects.\n */\nexport function teardownFxLifecycleListeners(rootElement: Element): void {\n const listeners: Array<{ event: string; handler: EventListener }> | undefined =\n (rootElement as any).__lvtFxLifecycleListeners;\n if (listeners) {\n listeners.forEach(({ event, handler }) => {\n document.removeEventListener(event, handler, true);\n });\n delete (rootElement as any).__lvtFxLifecycleListeners;\n }\n delete (rootElement as any).__lvtFxLifecycleSetup;\n}\n\n// \u2500\u2500\u2500 Implicit-trigger directive handlers (fire on every DOM update) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Apply scroll directives on elements with lvt-fx:scroll attributes.\n * Only processes attributes WITHOUT :on: suffix (implicit trigger).\n * Configuration read from CSS custom properties:\n * --lvt-scroll-behavior: auto | smooth (default: auto)\n * --lvt-scroll-threshold: <number> (default: 100)\n */\nconst VALID_SCROLL_BEHAVIORS = new Set([\"auto\", \"smooth\", \"instant\"]);\n\nexport function handleScrollDirectives(rootElement: Element): void {\n rootElement.querySelectorAll(\"[lvt-fx\\\\:scroll]\").forEach((element) => {\n const mode = element.getAttribute(\"lvt-fx:scroll\");\n if (!mode) return;\n applyFxEffect(element as HTMLElement, \"scroll\", mode);\n });\n}\n\n/**\n * Apply highlight directives to elements with lvt-fx:highlight attributes.\n * Configuration read from CSS custom properties:\n * --lvt-highlight-duration: <ms> (default: 500)\n * --lvt-highlight-color: <color> (default: #ffc107)\n */\nexport function handleHighlightDirectives(rootElement: Element): void {\n rootElement.querySelectorAll(\"[lvt-fx\\\\:highlight]\").forEach((element) => {\n const mode = element.getAttribute(\"lvt-fx:highlight\");\n if (!mode) return;\n applyFxEffect(element as HTMLElement, \"highlight\", mode);\n });\n}\n\n/**\n * Apply animation directives to elements with lvt-fx:animate attributes.\n * Configuration read from CSS custom properties:\n * --lvt-animate-duration: <ms> (default: 300)\n */\nexport function handleAnimateDirectives(rootElement: Element): void {\n rootElement.querySelectorAll(\"[lvt-fx\\\\:animate]\").forEach((element) => {\n const animation = element.getAttribute(\"lvt-fx:animate\");\n if (!animation) return;\n applyFxEffect(element as HTMLElement, \"animate\", animation);\n });\n\n ensureAnimateKeyframes();\n}\n\nfunction ensureAnimateKeyframes(): void {\n if (!document.getElementById(\"lvt-animate-styles\")) {\n const style = document.createElement(\"style\");\n style.id = \"lvt-animate-styles\";\n style.textContent = `\n @keyframes lvt-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n @keyframes lvt-slide-in {\n from { opacity: 0; transform: translateY(-10px); }\n to { opacity: 1; transform: translateY(0); }\n }\n @keyframes lvt-scale-in {\n from { opacity: 0; transform: scale(0.95); }\n to { opacity: 1; transform: scale(1); }\n }\n `;\n document.head.appendChild(style);\n }\n}\n\n// \u2500\u2500\u2500 Toast directives \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface ToastMessage {\n id: string;\n title?: string;\n body?: string;\n type: \"info\" | \"success\" | \"warning\" | \"error\";\n dismissible: boolean;\n dismissMS: number;\n}\n\n// Key used to store the last processed data-pending value on each trigger element.\n// Prevents showing the same batch of toasts twice if handleToastDirectives is\n// called multiple times within a single update cycle (e.g. from multiple patches).\nconst PENDING_PROCESSED_KEY = \"__lvtPendingProcessed\";\n\n/**\n * Read data-pending toast messages from server trigger elements and create\n * client-managed toast DOM. Called after each LiveTemplate DOM update.\n */\nexport function handleToastDirectives(rootElement: Element): void {\n rootElement\n .querySelectorAll<HTMLElement>(\"[data-toast-trigger]\")\n .forEach((trigger) => {\n const pending = trigger.getAttribute(\"data-pending\");\n if (!pending) return;\n // Skip if this exact batch was already processed (handles multi-patch calls)\n if ((trigger as any)[PENDING_PROCESSED_KEY] === pending) return;\n (trigger as any)[PENDING_PROCESSED_KEY] = pending;\n\n let messages: ToastMessage[];\n try {\n messages = JSON.parse(pending);\n } catch {\n return;\n }\n if (!Array.isArray(messages) || !messages.length) return;\n\n const position = trigger.getAttribute(\"data-position\") || \"top-right\";\n const stack = getOrCreateToastStack(position);\n messages.forEach((msg) => {\n const el = createToastElement(msg);\n stack.appendChild(el);\n if (typeof msg.dismissMS === \"number\" && msg.dismissMS > 0) {\n setTimeout(() => el.remove(), msg.dismissMS);\n }\n });\n });\n}\n\n/**\n * Set up a document click listener that dismisses all visible toasts when\n * the user clicks outside the toast stack. Called once at connect time.\n */\nexport function setupToastClickOutside(): void {\n const key = \"__lvt_toast_click_outside\";\n const existing = (document as any)[key];\n if (existing) document.removeEventListener(\"click\", existing);\n const listener = (e: Event) => {\n const stack = document.querySelector(\"[data-lvt-toast-stack]\");\n if (!stack || stack.contains(e.target as Node)) return;\n stack.querySelectorAll(\"[data-lvt-toast-item]\").forEach((el) => el.remove());\n };\n (document as any)[key] = listener;\n document.addEventListener(\"click\", listener);\n}\n\nfunction getOrCreateToastStack(position: string): HTMLElement {\n let stack = document.querySelector(\n \"[data-lvt-toast-stack]\"\n ) as HTMLElement | null;\n if (!stack) {\n stack = document.createElement(\"div\");\n stack.setAttribute(\"data-lvt-toast-stack\", \"\");\n stack.setAttribute(\"aria-live\", \"polite\");\n applyPositionStyles(stack, position);\n document.body.appendChild(stack);\n }\n return stack;\n}\n\nfunction applyPositionStyles(stack: HTMLElement, position: string): void {\n const s = stack.style;\n switch (position) {\n case \"top-left\":\n s.top = \"1rem\"; s.left = \"1rem\"; break;\n case \"top-center\":\n s.top = \"1rem\"; s.left = \"50%\"; s.transform = \"translateX(-50%)\"; break;\n case \"bottom-right\":\n s.bottom = \"1rem\"; s.right = \"1rem\"; break;\n case \"bottom-left\":\n s.bottom = \"1rem\"; s.left = \"1rem\"; break;\n case \"bottom-center\":\n s.bottom = \"1rem\"; s.left = \"50%\"; s.transform = \"translateX(-50%)\"; break;\n default: // top-right\n s.top = \"1rem\"; s.right = \"1rem\"; break;\n }\n}\n\nfunction createToastElement(msg: ToastMessage): HTMLElement {\n const el = document.createElement(\"div\");\n el.setAttribute(\"role\", \"alert\");\n el.setAttribute(\"data-lvt-toast-item\", msg.id);\n if (msg.type) el.setAttribute(\"data-type\", msg.type);\n\n const inner = document.createElement(\"div\");\n inner.setAttribute(\"data-lvt-toast-content\", \"\");\n\n if (msg.title) {\n const t = document.createElement(\"strong\");\n t.textContent = msg.title;\n inner.appendChild(t);\n }\n if (msg.body) {\n const b = document.createElement(\"p\");\n b.textContent = msg.body;\n inner.appendChild(b);\n }\n\n el.appendChild(inner);\n\n if (msg.dismissible) {\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.setAttribute(\"aria-label\", \"Dismiss\");\n btn.textContent = \"\u00D7\";\n btn.addEventListener(\"click\", () => el.remove());\n el.appendChild(btn);\n }\n\n return el;\n}\n\n", "/**\n * Debounce function: delays execution until after a pause in calls\n */\nexport function debounce<T extends (...args: any[]) => any>(\n func: T,\n wait: number\n): (...args: Parameters<T>) => void {\n let timeout: number | null = null;\n\n return function debounceWrapper(this: unknown, ...args: Parameters<T>) {\n const context = this;\n\n if (timeout !== null) {\n clearTimeout(timeout);\n }\n\n timeout = window.setTimeout(() => {\n func.apply(context, args);\n }, wait);\n };\n}\n\n/**\n * Throttle function: limits execution to at most once per time period\n * First call executes immediately, subsequent calls are delayed\n */\nexport function throttle<T extends (...args: any[]) => any>(\n func: T,\n limit: number\n): (...args: Parameters<T>) => void {\n let inThrottle = false;\n\n return function throttleWrapper(this: unknown, ...args: Parameters<T>) {\n const context = this;\n\n if (!inThrottle) {\n func.apply(context, args);\n inThrottle = true;\n\n setTimeout(() => {\n inThrottle = false;\n }, limit);\n }\n };\n}\n", "/** Escapes colons in attribute names for use in CSS attribute selectors. */\nexport function lvtSelector(attr: string, value?: string): string {\n const escaped = attr.replace(/:/g, \"\\\\:\");\n if (value === undefined) return `[${escaped}]`;\n // Escape backslashes and double-quotes in the value to prevent CSS selector injection\n const safeValue = value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n return `[${escaped}=\"${safeValue}\"]`;\n}\n", "import { debounce, throttle } from \"../utils/rate-limit\";\nimport { lvtSelector } from \"../utils/lvt-selector\";\nimport { executeAction, resolveTarget, processElementInteraction, isDOMEventTrigger, type ReactiveAction } from \"./reactive-attributes\";\nimport type { Logger } from \"../utils/logger\";\n\n// Methods supported by click-away, derived from ReactiveAction values\nconst CLICK_AWAY_METHOD_MAP: Record<string, ReactiveAction> = {\n reset: \"reset\",\n addclass: \"addClass\",\n removeclass: \"removeClass\",\n toggleclass: \"toggleClass\",\n setattr: \"setAttr\",\n toggleattr: \"toggleAttr\",\n};\nconst CLICK_AWAY_METHODS = Object.keys(CLICK_AWAY_METHOD_MAP);\n\n// Non-bubbling events need direct attachment rather than wrapper delegation\nconst NON_BUBBLING = new Set([\"mouseenter\", \"mouseleave\", \"focus\", \"blur\"]);\n\nexport interface EventDelegationContext {\n getWrapperElement(): Element | null;\n getRateLimitedHandlers(): WeakMap<Element, Map<string, Function>>;\n parseValue(value: string): any;\n send(message: any): void;\n sendHTTPMultipart(form: HTMLFormElement, action: string, formData: FormData): void;\n setActiveSubmission(\n form: HTMLFormElement | null,\n button: HTMLButtonElement | null,\n originalButtonText: string | null\n ): void;\n getWebSocketReadyState(): number | undefined;\n triggerPendingUploads(uploadName: string): void;\n}\n\n/**\n * Handles all DOM event delegation concerns for LiveTemplateClient.\n */\n// All event types registered by setupEventDelegation. Exported so\n// teardownForWrapper() can remove them all without drift.\nexport const DELEGATED_EVENT_TYPES = [\n \"click\",\n \"submit\",\n \"change\",\n \"input\",\n \"search\",\n \"keydown\",\n \"keyup\",\n \"focus\",\n \"blur\",\n \"mouseenter\",\n \"mouseleave\",\n] as const;\n\nexport class EventDelegator {\n constructor(\n private readonly context: EventDelegationContext,\n private readonly logger: Logger\n ) {}\n\n /**\n * Remove all document-level event listeners registered by\n * setupEventDelegation for a specific wrapper ID. Call this before\n * a cross-handler navigation changes the wrapper's data-lvt-id, to\n * prevent listener leaks.\n */\n teardownForWrapper(wrapperId: string | null): void {\n if (!wrapperId) return;\n for (const eventType of DELEGATED_EVENT_TYPES) {\n const listenerKey = `__lvt_delegated_${eventType}_${wrapperId}`;\n const existingListener = (document as any)[listenerKey];\n if (existingListener) {\n document.removeEventListener(eventType, existingListener, false);\n delete (document as any)[listenerKey];\n }\n }\n }\n\n private extractButtonData(button: HTMLButtonElement | HTMLInputElement, data: Record<string, any>): void {\n if (button.value) {\n data.value = this.context.parseValue(button.value);\n }\n Array.from(button.attributes).forEach((attr) => {\n if (attr.name.startsWith(\"data-\") && attr.name !== \"data-key\") {\n const key = attr.name.slice(5);\n data[key] = this.context.parseValue(attr.value);\n }\n });\n }\n\n setupEventDelegation(): void {\n const wrapperElement = this.context.getWrapperElement();\n if (!wrapperElement) return;\n\n const eventTypes = DELEGATED_EVENT_TYPES;\n const wrapperId = wrapperElement.getAttribute(\"data-lvt-id\");\n const rateLimitedHandlers = this.context.getRateLimitedHandlers();\n\n eventTypes.forEach((eventType) => {\n const listenerKey = `__lvt_delegated_${eventType}_${wrapperId}`;\n const existingListener = (document as any)[listenerKey];\n if (existingListener) {\n document.removeEventListener(eventType, existingListener, false);\n }\n\n const listener = (e: Event) => {\n const currentWrapper = this.context.getWrapperElement();\n if (!currentWrapper) return;\n\n const target = e.target as Element;\n\n this.logger.debug(\"Event listener triggered:\", eventType, e.target);\n\n if (!target) return;\n\n let element: Element | null = target;\n let inWrapper = false;\n\n while (element) {\n if (element === currentWrapper) {\n inWrapper = true;\n break;\n }\n element = element.parentElement;\n }\n\n if (!inWrapper) return;\n\n const attrName = `lvt-on:${eventType}`;\n element = target;\n\n while (element && element !== currentWrapper.parentElement) {\n let action = element.getAttribute(attrName);\n let actionElement = element;\n let isOrphanButton = false;\n\n // Check for lvt-persist on form submit (auto-persist to database)\n if (!action && eventType === \"submit\" && element instanceof HTMLFormElement) {\n const persistTable = element.getAttribute(\"lvt-persist\");\n if (persistTable) {\n action = `persist:${persistTable}`;\n actionElement = element;\n }\n }\n\n // Orphan button detection (Tier 1: formless standalone buttons).\n // A <button name=\"action\"> outside any form triggers the named action directly.\n // Resolution order for click events:\n // 1. lvt-on:click attribute (Tier 2 \u2014 already checked above)\n // 2. Orphan button name (Tier 1 \u2014 checked here)\n if (!action && eventType === \"click\") {\n const btn = element instanceof HTMLButtonElement ? element : null;\n if (\n btn &&\n btn.name &&\n !btn.disabled &&\n btn.type !== \"reset\" &&\n btn.form === null &&\n !btn.hasAttribute(\"commandfor\")\n ) {\n action = btn.name;\n actionElement = btn;\n isOrphanButton = true;\n }\n }\n\n // Auto-intercept forms (progressive complexity).\n // Action resolution order:\n // 1. lvt-form:action attribute (explicit routing)\n // 2. submitter.name (button name = action)\n // 3. form.name (form name = action)\n // 4. \"submit\" (server defaults to Submit())\n //\n // Note: lvt-action hidden field is a server-side progressive\n // enhancement fallback (no-JS POST). The client does not read it;\n // the server extracts it from form data directly.\n if (!action && eventType === \"submit\" && element instanceof HTMLFormElement) {\n if (!element.hasAttribute(\"lvt-form:no-intercept\")) {\n // Check for explicit routing attribute first.\n // Empty string (\"\") falls through to submitter/form name resolution.\n const explicitAction = element.getAttribute(\"lvt-form:action\");\n const submitter = (e as SubmitEvent).submitter;\n if (explicitAction) {\n action = explicitAction;\n } else {\n if (submitter instanceof HTMLButtonElement && submitter.name) {\n action = submitter.name;\n } else if (element.getAttribute(\"name\")) {\n action = element.getAttribute(\"name\")!;\n } else {\n action = \"submit\";\n }\n }\n actionElement = element;\n\n if (submitter) {\n (element as any).__lvtSubmitter = submitter;\n }\n\n // Dialog support: forms with method=\"dialog\" inside <dialog>\n // close the dialog AND route the action to the server.\n const dialog = element.closest(\"dialog\");\n if (dialog && element.getAttribute(\"method\")?.toLowerCase() === \"dialog\") {\n (element as any).__lvtCloseDialog = dialog;\n }\n }\n }\n\n if (action != null && actionElement) {\n if (eventType === \"submit\") {\n e.preventDefault();\n }\n\n if (\n (eventType === \"keydown\" || eventType === \"keyup\") &&\n actionElement.hasAttribute(\"lvt-key\")\n ) {\n const keyFilter = actionElement.getAttribute(\"lvt-key\");\n const keyboardEvent = e as KeyboardEvent;\n if (keyFilter && keyboardEvent.key !== keyFilter) {\n element = element.parentElement;\n continue;\n }\n }\n\n const targetElement = actionElement;\n\n const handleAction = () => {\n this.logger.debug(\"handleAction called\", {\n action,\n eventType,\n targetElement,\n });\n\n const message: any = { action, data: {} };\n\n if (targetElement instanceof HTMLFormElement) {\n this.logger.debug(\"Processing form element\");\n const formData = new FormData(targetElement);\n\n const checkboxes = Array.from(\n targetElement.querySelectorAll('input[type=\"checkbox\"][name]')\n ) as HTMLInputElement[];\n const checkboxNames = new Set(checkboxes.map((cb) => cb.name));\n\n checkboxNames.forEach((name) => {\n message.data[name] = false;\n });\n\n // Get password field names to skip parseValue for them\n const passwordFields = new Set(\n Array.from(\n targetElement.querySelectorAll('input[type=\"password\"][name]')\n ).map((el) => (el as HTMLInputElement).name)\n );\n\n // Exclude the submitter button's name from form data.\n // The submitter's name is used as the action routing key in the\n // button-name path \u2014 including it in data would be redundant.\n // When lvt-form:action overrides routing, the button name is still\n // excluded to avoid noisy payloads (the button is a UI control,\n // not domain data). Button value and data-* attrs are collected below.\n // \"action\" is NOT excluded \u2014 it's a normal data field.\n const submitterForData = (targetElement as any).__lvtSubmitter as HTMLButtonElement | undefined;\n const actionFieldName = submitterForData?.name;\n\n formData.forEach((value, key) => {\n if (value instanceof File) return; // Skip file entries \u2014 handled by sendHTTPMultipart\n if (actionFieldName && key === actionFieldName) return;\n if (checkboxNames.has(key)) {\n message.data[key] = true;\n this.logger.debug(\"Converted checkbox\", key, \"to true\");\n } else if (passwordFields.has(key)) {\n // Never parse password values - always keep as string\n message.data[key] = value as string;\n } else {\n message.data[key] = this.context.parseValue(\n value as string\n );\n }\n });\n\n // Collect data from the submitter button:\n // - button value \u2192 data.value (e.g., <button name=\"delete\" value=\"{{.ID}}\">)\n // - data-* attributes \u2192 data keys\n const submitter2 = (targetElement as any).__lvtSubmitter as HTMLButtonElement | undefined;\n if (submitter2) {\n this.extractButtonData(submitter2, message.data);\n delete (targetElement as any).__lvtSubmitter;\n }\n\n this.logger.debug(\"Form data collected:\", message.data);\n } else if (eventType === \"change\" || eventType === \"input\" || eventType === \"search\") {\n if (targetElement instanceof HTMLInputElement) {\n const key = targetElement.name || \"value\";\n message.data[key] = this.context.parseValue(\n targetElement.value\n );\n } else if (targetElement instanceof HTMLSelectElement) {\n const key = targetElement.name || \"value\";\n message.data[key] = this.context.parseValue(\n targetElement.value\n );\n } else if (targetElement instanceof HTMLTextAreaElement) {\n const key = targetElement.name || \"value\";\n message.data[key] = this.context.parseValue(\n targetElement.value\n );\n }\n }\n\n if (isOrphanButton) {\n this.extractButtonData(actionElement as HTMLButtonElement, message.data);\n }\n\n // Extract standard data-* attributes from the action element.\n // Exclude data-key (list reconciliation) and data-lvt-id (internal framework ID)\n // since these are LiveTemplate internals, not user-provided action data.\n if (!(targetElement instanceof HTMLFormElement) && !isOrphanButton) {\n Array.from(actionElement.attributes).forEach((attr) => {\n if (attr.name.startsWith(\"data-\") && attr.name !== \"data-key\" && attr.name !== \"data-lvt-id\") {\n const key = attr.name.slice(5);\n message.data[key] = this.context.parseValue(attr.value);\n }\n });\n }\n\n // Tier 1 file upload detection \u2014 must happen BEFORE setActiveSubmission\n // which disables the fieldset. Once the fieldset is disabled, FormData\n // excludes all its child fields, so we must build FormData here if we\n // need it for HTTP multipart upload.\n let tier1FormData: FormData | null = null;\n if (\n eventType === \"submit\" &&\n targetElement instanceof HTMLFormElement\n ) {\n const tier1FileInputs = targetElement.querySelectorAll<HTMLInputElement>(\n 'input[type=\"file\"]:not([lvt-upload])'\n );\n const hasFiles = Array.from(tier1FileInputs).some(\n (input) => input.files && input.files.length > 0\n );\n if (hasFiles) {\n // Include the submitter's name/value so multi-button\n // forms preserve the clicked button's entry (e.g.\n // <button name=\"action\" value=\"save\">). We avoid the\n // two-argument FormData constructor (Chrome 112+,\n // Firefox 111+, Safari 16.4+ \u2014 March 2023) and set\n // the submitter value manually for broader browser\n // compatibility.\n const submitter = (e as SubmitEvent).submitter as\n | HTMLButtonElement\n | HTMLInputElement\n | null;\n tier1FormData = new FormData(targetElement);\n if (submitter?.name) {\n tier1FormData.set(submitter.name, submitter.value);\n }\n // Set the action field here (not inside sendHTTPMultipart)\n // so the client-owned FormData is fully prepared before\n // being passed to another method. This keeps\n // sendHTTPMultipart from mutating a caller-supplied\n // FormData object.\n tier1FormData.set(\"lvt-action\", action);\n }\n }\n\n if (\n eventType === \"submit\" &&\n targetElement instanceof HTMLFormElement\n ) {\n const submitEvent = e as SubmitEvent;\n const submitButton =\n submitEvent.submitter as HTMLButtonElement | null;\n let originalButtonText: string | null = null;\n\n if (\n submitButton &&\n submitButton.hasAttribute(\"lvt-form:disable-with\")\n ) {\n originalButtonText = submitButton.textContent;\n submitButton.disabled = true;\n submitButton.textContent =\n submitButton.getAttribute(\"lvt-form:disable-with\");\n this.logger.debug(\"Disabled submit button\");\n }\n\n this.context.setActiveSubmission(\n targetElement,\n submitButton || null,\n originalButtonText\n );\n\n // Trigger pending uploads for any file inputs in the form\n const fileInputs = targetElement.querySelectorAll<HTMLInputElement>(\n 'input[type=\"file\"][lvt-upload]'\n );\n fileInputs.forEach((input) => {\n const uploadName = input.getAttribute(\"lvt-upload\");\n if (uploadName) {\n this.logger.debug(\"Triggering pending uploads for:\", uploadName);\n this.context.triggerPendingUploads(uploadName);\n }\n });\n\n targetElement.dispatchEvent(\n new CustomEvent(\"lvt:pending\", { detail: message })\n );\n this.logger.debug(\"Emitted lvt:pending event\");\n }\n\n this.logger.debug(\"About to send message:\", message);\n this.logger.debug(\n \"WebSocket state:\",\n this.context.getWebSocketReadyState()\n );\n\n // Tier 1 file uploads: send via HTTP fetch with FormData captured\n // BEFORE the fieldset was disabled by setActiveSubmission.\n // tier1FormData is only set when targetElement is a form.\n if (tier1FormData !== null) {\n this.logger.debug(\"Tier 1 file upload detected, using HTTP fetch\");\n this.context.sendHTTPMultipart(\n targetElement as HTMLFormElement,\n action,\n tier1FormData\n );\n return;\n }\n\n this.context.send(message);\n this.logger.debug(\"send() called\");\n\n // Close dialog if this was a method=\"dialog\" form inside <dialog>\n if (targetElement instanceof HTMLFormElement) {\n const dialogToClose = (targetElement as any).__lvtCloseDialog as HTMLDialogElement | undefined;\n if (dialogToClose) {\n dialogToClose.close();\n delete (targetElement as any).__lvtCloseDialog;\n }\n }\n };\n\n const throttleValue = actionElement.getAttribute(\"lvt-mod:throttle\");\n const debounceValue = actionElement.getAttribute(\"lvt-mod:debounce\");\n\n // Skip rate limiting for \"search\" event (clear button click) - it's a discrete action\n const shouldRateLimit = (throttleValue || debounceValue) && eventType !== \"search\";\n\n if (shouldRateLimit) {\n if (!rateLimitedHandlers.has(actionElement)) {\n rateLimitedHandlers.set(actionElement, new Map());\n }\n const handlerCache = rateLimitedHandlers.get(actionElement)!;\n const cacheKey = `${eventType}:${action}`;\n\n // Store callback reference on the element itself to avoid type issues with the Map\n // This allows us to update the callback each event while reusing the debounced timer\n const callbackRefKey = `__lvt_callback_${cacheKey}`;\n const elementWithCallback = actionElement as HTMLElement & { [key: string]: { current: () => void } };\n if (!elementWithCallback[callbackRefKey]) {\n elementWithCallback[callbackRefKey] = { current: handleAction };\n }\n // Always update to the latest handleAction (with fresh closure capturing current values)\n elementWithCallback[callbackRefKey].current = handleAction;\n\n let rateLimitedHandler = handlerCache.get(cacheKey);\n if (!rateLimitedHandler) {\n // Create rate-limited function that calls the CURRENT callback via reference\n // This way, when the debounce timer fires, it uses the latest captured values\n const callLatest = () => elementWithCallback[callbackRefKey].current();\n if (throttleValue) {\n const limit = parseInt(throttleValue, 10);\n rateLimitedHandler = throttle(callLatest, limit);\n } else if (debounceValue) {\n const wait = parseInt(debounceValue, 10);\n rateLimitedHandler = debounce(callLatest, wait);\n }\n if (rateLimitedHandler) {\n handlerCache.set(cacheKey, rateLimitedHandler);\n }\n }\n\n if (rateLimitedHandler) {\n rateLimitedHandler();\n }\n } else {\n handleAction();\n }\n\n return;\n }\n element = element.parentElement;\n }\n };\n\n (document as any)[listenerKey] = listener;\n document.addEventListener(eventType, listener, false);\n this.logger.debug(\n \"Registered event listener:\",\n eventType,\n \"with key:\",\n listenerKey\n );\n });\n }\n\n setupWindowEventDelegation(): void {\n const wrapperElement = this.context.getWrapperElement();\n if (!wrapperElement) return;\n\n const windowEvents = [\n \"keydown\",\n \"keyup\",\n \"scroll\",\n \"resize\",\n \"focus\",\n \"blur\",\n ];\n const wrapperId = wrapperElement.getAttribute(\"data-lvt-id\");\n const rateLimitedHandlers = this.context.getRateLimitedHandlers();\n\n windowEvents.forEach((eventType) => {\n const listenerKey = `__lvt_window_${eventType}_${wrapperId}`;\n const existingListener = (window as any)[listenerKey];\n if (existingListener) {\n window.removeEventListener(eventType, existingListener);\n }\n\n const listener = (e: Event) => {\n const currentWrapper = this.context.getWrapperElement();\n if (!currentWrapper) return;\n\n const attrName = `lvt-on:window:${eventType}`;\n const elements = currentWrapper.querySelectorAll(lvtSelector(attrName));\n\n elements.forEach((element) => {\n const action = element.getAttribute(attrName);\n if (!action) return;\n\n if (\n (eventType === \"keydown\" || eventType === \"keyup\") &&\n element.hasAttribute(\"lvt-key\")\n ) {\n const keyFilter = element.getAttribute(\"lvt-key\");\n const keyboardEvent = e as KeyboardEvent;\n if (keyFilter && keyboardEvent.key !== keyFilter) {\n return;\n }\n }\n\n const message: any = { action, data: {} };\n\n // Extract standard data-* attributes from element\n Array.from(element.attributes).forEach((attr) => {\n if (attr.name.startsWith(\"data-\") && attr.name !== \"data-key\" && attr.name !== \"data-lvt-id\") {\n const key = attr.name.slice(5);\n message.data[key] = this.context.parseValue(attr.value);\n }\n });\n\n const throttleValue = element.getAttribute(\"lvt-mod:throttle\");\n const debounceValue = element.getAttribute(\"lvt-mod:debounce\");\n\n const handleAction = () => this.context.send(message);\n\n if (throttleValue || debounceValue) {\n if (!rateLimitedHandlers.has(element)) {\n rateLimitedHandlers.set(element, new Map());\n }\n const handlerCache = rateLimitedHandlers.get(element)!;\n const cacheKey = `window-${eventType}:${action}`;\n\n let rateLimitedHandler = handlerCache.get(cacheKey);\n if (!rateLimitedHandler) {\n if (throttleValue) {\n const limit = parseInt(throttleValue, 10);\n rateLimitedHandler = throttle(handleAction, limit);\n } else if (debounceValue) {\n const wait = parseInt(debounceValue, 10);\n rateLimitedHandler = debounce(handleAction, wait);\n }\n if (rateLimitedHandler) {\n handlerCache.set(cacheKey, rateLimitedHandler);\n }\n }\n\n if (rateLimitedHandler) {\n rateLimitedHandler();\n }\n } else {\n handleAction();\n }\n });\n };\n\n (window as any)[listenerKey] = listener;\n window.addEventListener(eventType, listener);\n });\n }\n\n /**\n * Sets up click-away detection for lvt-el:*:on:click-away attributes.\n * Instead of routing to a server action, click-away triggers client-side\n * DOM manipulation via executeAction from reactive-attributes.\n */\n setupClickAwayDelegation(): void {\n const wrapperElement = this.context.getWrapperElement();\n if (!wrapperElement) return;\n\n const wrapperId = wrapperElement.getAttribute(\"data-lvt-id\");\n const listenerKey = `__lvt_click_away_${wrapperId}`;\n const existingListener = (document as any)[listenerKey];\n if (existingListener) {\n document.removeEventListener(\"click\", existingListener);\n }\n\n const listener = (e: Event) => {\n const currentWrapper = this.context.getWrapperElement();\n if (!currentWrapper) return;\n\n const target = e.target as Element;\n\n const clickAwaySelector = CLICK_AWAY_METHODS\n .map(m => lvtSelector(`lvt-el:${m}:on:click-away`))\n .join(\", \");\n const clickAwayElements = currentWrapper.querySelectorAll(clickAwaySelector);\n clickAwayElements.forEach((element) => {\n if (element.contains(target)) return; // Click was inside, not away\n\n Array.from(element.attributes).forEach((attr) => {\n if (!attr.name.includes(\":on:click-away\")) return;\n const match = attr.name.match(/^lvt-el:(\\w+):on:click-away$/);\n if (!match) return;\n const method = CLICK_AWAY_METHOD_MAP[match[1].toLowerCase()];\n if (!method) return;\n executeAction(resolveTarget(element), method, attr.value);\n });\n });\n };\n\n (document as any)[listenerKey] = listener;\n document.addEventListener(\"click\", listener);\n }\n\n /**\n * Sets up event listeners for lvt-el:*:on:{event} attributes where {event}\n * is a native DOM event (not a lifecycle state or synthetic trigger).\n *\n * Scans scanRoot (or the full wrapper if omitted) for elements with these\n * attributes. Attaches direct listeners for non-bubbling events (mouseenter,\n * mouseleave) and delegated listeners on the wrapper for bubbling events\n * (click, focusin, focusout, etc.).\n *\n * Bubbling delegation uses closest-match semantics: if both a child and parent\n * have the same trigger, only the child's action fires. This differs from native\n * event bubbling and prevents unintended double-firing in nested structures.\n *\n * Called during connect and after each DOM update to handle new elements.\n *\n * @param scanRoot - Subtree to scan for new attributes. Defaults to full wrapper.\n * Pass the updated element after a DOM patch to avoid a full rescan.\n */\n setupDOMEventTriggerDelegation(scanRoot?: Element): void {\n const wrapperElement = this.context.getWrapperElement();\n if (!wrapperElement) return;\n\n const wrapperId = wrapperElement.getAttribute(\"data-lvt-id\");\n if (!wrapperId) return;\n // Track which bubbling events we've already delegated at wrapper level\n const delegatedKey = `__lvt_el_delegated_${wrapperId}`;\n const delegated: Set<string> = (wrapperElement as any)[delegatedKey] || new Set();\n\n // Track all listeners (direct + delegated) on wrapper for teardown\n // Prune stale entries from elements replaced by morphdom\n const listenersKey = `__lvt_el_listeners_${wrapperId}`;\n const allListeners: Array<{ el: Element; event: string; handler: EventListener; guardKey?: string }> =\n ((wrapperElement as any)[listenersKey] || []).filter(\n (entry: { el: Element }) => entry.el.isConnected\n );\n\n // Scan the provided subtree (or full wrapper) for lvt-el:*:on:{event} attributes.\n // Process root then descendants (avoids spreading NodeList into array).\n const root = scanRoot || wrapperElement;\n const processEl = (el: Element) => {\n const triggers = new Set<string>();\n for (const attr of el.attributes) {\n if (!attr.name.startsWith(\"lvt-el:\")) continue;\n const match = attr.name.match(/^lvt-el:\\w+:on:([a-z-]+)$/i);\n if (!match) continue;\n const trigger = match[1].toLowerCase();\n if (!isDOMEventTrigger(trigger)) continue;\n triggers.add(trigger);\n }\n\n for (const trigger of triggers) {\n if (NON_BUBBLING.has(trigger)) {\n // Direct attachment for non-bubbling events\n const key = `__lvt_el_${trigger}`;\n if ((el as any)[key]) continue; // already attached\n const listener = () => processElementInteraction(el, trigger);\n el.addEventListener(trigger, listener);\n (el as any)[key] = listener;\n allListeners.push({ el, event: trigger, handler: listener, guardKey: key });\n } else if (!delegated.has(trigger)) {\n // Delegated listener on wrapper for bubbling events.\n // Walks from target to wrapper, processing only the closest matching element.\n const escaped = trigger.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const triggerPattern = new RegExp(`^lvt-el:\\\\w+:on:${escaped}$`, \"i\");\n const handler = (e: Event) => {\n let target = e.target as Element | null;\n while (target && target !== wrapperElement) {\n let hasMatch = false;\n for (const a of target.attributes) {\n if (triggerPattern.test(a.name)) { hasMatch = true; break; }\n }\n if (hasMatch) {\n processElementInteraction(target, trigger);\n return; // Stop at closest match\n }\n target = target.parentElement;\n }\n // Also check wrapper itself\n if (target === wrapperElement) {\n processElementInteraction(wrapperElement, trigger);\n }\n };\n wrapperElement.addEventListener(trigger, handler);\n delegated.add(trigger);\n allListeners.push({ el: wrapperElement, event: trigger, handler });\n }\n }\n };\n processEl(root);\n root.querySelectorAll(\"*\").forEach(processEl);\n\n (wrapperElement as any)[listenersKey] = allListeners;\n (wrapperElement as any)[delegatedKey] = delegated;\n }\n\n /**\n * Remove delegated DOM event trigger listeners added by setupDOMEventTriggerDelegation.\n * Call on disconnect to prevent stale listeners firing on a disconnected component.\n */\n teardownDOMEventTriggerDelegation(): void {\n const wrapperElement = this.context.getWrapperElement();\n if (!wrapperElement) return;\n\n const wrapperId = wrapperElement.getAttribute(\"data-lvt-id\");\n if (!wrapperId) return;\n\n const listenersKey = `__lvt_el_listeners_${wrapperId}`;\n const listeners: Array<{ el: Element; event: string; handler: EventListener; guardKey?: string }> | undefined =\n (wrapperElement as any)[listenersKey];\n if (listeners) {\n listeners.forEach(({ el, event, handler, guardKey }) => {\n el.removeEventListener(event, handler);\n if (guardKey) delete (el as any)[guardKey];\n });\n delete (wrapperElement as any)[listenersKey];\n }\n\n const delegatedKey = `__lvt_el_delegated_${wrapperId}`;\n delete (wrapperElement as any)[delegatedKey];\n }\n\n /**\n * Sets up focus trapping for elements with lvt-focus-trap attribute.\n * Focus is trapped within the element, cycling through focusable elements\n * when Tab/Shift+Tab is pressed.\n */\n setupFocusTrapDelegation(): void {\n const wrapperElement = this.context.getWrapperElement();\n if (!wrapperElement) return;\n\n const wrapperId = wrapperElement.getAttribute(\"data-lvt-id\");\n const listenerKey = `__lvt_focus_trap_${wrapperId}`;\n const existingListener = (document as any)[listenerKey];\n if (existingListener) {\n document.removeEventListener(\"keydown\", existingListener);\n }\n\n const getFocusableElements = (container: Element): HTMLElement[] => {\n const selector = [\n 'a[href]:not([disabled])',\n 'button:not([disabled])',\n 'textarea:not([disabled])',\n 'input:not([disabled]):not([type=\"hidden\"])',\n 'select:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"]):not([disabled])',\n '[contenteditable=\"true\"]'\n ].join(', ');\n\n return Array.from(container.querySelectorAll(selector)).filter(\n (el) => {\n const htmlEl = el as HTMLElement;\n // Check if element is visible\n const style = window.getComputedStyle(htmlEl);\n const isNotDisplayNone = style.display !== 'none';\n const isNotVisibilityHidden = style.visibility !== 'hidden';\n // offsetParent can be null in JSDOM or for fixed/absolute positioned elements\n const hasLayoutContext = htmlEl.offsetParent !== null ||\n style.position === 'fixed' ||\n style.position === 'absolute' ||\n // In test environments, offsetParent may always be null\n (typeof process !== 'undefined' && (process as any).env?.NODE_ENV === 'test');\n return isNotDisplayNone && isNotVisibilityHidden && hasLayoutContext;\n }\n ) as HTMLElement[];\n };\n\n const listener = (e: KeyboardEvent) => {\n if (e.key !== \"Tab\") return;\n\n const currentWrapper = this.context.getWrapperElement();\n if (!currentWrapper) return;\n\n // Find the active focus trap container (innermost one containing the focused element)\n const focusTrapElements = currentWrapper.querySelectorAll(\"[lvt-focus-trap]\");\n let activeTrap: Element | null = null;\n\n focusTrapElements.forEach((trap) => {\n if (trap.contains(document.activeElement)) {\n // Check if this is the innermost trap containing the focused element\n if (!activeTrap || trap.contains(activeTrap)) {\n activeTrap = trap;\n }\n }\n });\n\n // If there's a visible focus trap that doesn't contain the active element,\n // and is visible, trap focus there (for newly opened modals/dropdowns)\n if (!activeTrap) {\n focusTrapElements.forEach((trap) => {\n const htmlTrap = trap as HTMLElement;\n const style = window.getComputedStyle(htmlTrap);\n if (style.display !== 'none' && style.visibility !== 'hidden') {\n activeTrap = trap;\n }\n });\n }\n\n if (!activeTrap) return;\n\n const focusableElements = getFocusableElements(activeTrap);\n if (focusableElements.length === 0) return;\n\n const firstElement = focusableElements[0];\n const lastElement = focusableElements[focusableElements.length - 1];\n\n if (e.shiftKey) {\n // Shift+Tab: moving backwards\n if (document.activeElement === firstElement) {\n e.preventDefault();\n lastElement.focus();\n }\n } else {\n // Tab: moving forwards\n if (document.activeElement === lastElement) {\n e.preventDefault();\n firstElement.focus();\n }\n }\n };\n\n (document as any)[listenerKey] = listener;\n document.addEventListener(\"keydown\", listener);\n this.logger.debug(\"Focus trap delegation set up\");\n }\n\n /**\n * Sets up autofocus for elements with lvt-autofocus attribute.\n * Automatically focuses the first element with lvt-autofocus when it becomes visible.\n * Uses MutationObserver to detect when elements are added or become visible.\n */\n setupAutofocusDelegation(): void {\n const wrapperElement = this.context.getWrapperElement();\n if (!wrapperElement) return;\n\n const wrapperId = wrapperElement.getAttribute(\"data-lvt-id\");\n const observerKey = `__lvt_autofocus_observer_${wrapperId}`;\n\n // Disconnect existing observer if any\n const existingObserver = (wrapperElement as any)[observerKey];\n if (existingObserver) {\n existingObserver.disconnect();\n }\n\n const processAutofocus = () => {\n const currentWrapper = this.context.getWrapperElement();\n if (!currentWrapper) return;\n\n // Find all elements with lvt-autofocus that are visible\n const autofocusElements = currentWrapper.querySelectorAll(\"[lvt-autofocus]\");\n\n autofocusElements.forEach((element) => {\n const htmlElement = element as HTMLElement;\n const style = window.getComputedStyle(htmlElement);\n\n // Check if element is visible and hasn't been focused yet in this visibility state\n // Note: offsetParent can be null in JSDOM or for fixed/absolute positioned elements,\n // so we only use it as a secondary check when it's available\n const isNotDisplayNone = style.display !== 'none';\n const isNotVisibilityHidden = style.visibility !== 'hidden';\n const hasLayoutContext = htmlElement.offsetParent !== null ||\n style.position === 'fixed' ||\n style.position === 'absolute' ||\n htmlElement.tagName === 'BODY' ||\n // In test environments, offsetParent may always be null\n (typeof process !== 'undefined' && (process as any).env?.NODE_ENV === 'test');\n const isVisible = isNotDisplayNone && isNotVisibilityHidden && hasLayoutContext;\n\n const wasFocused = htmlElement.getAttribute(\"data-lvt-autofocused\") === \"true\";\n\n if (isVisible && !wasFocused) {\n // Mark as focused to prevent re-focusing on every mutation\n htmlElement.setAttribute(\"data-lvt-autofocused\", \"true\");\n\n // Use requestAnimationFrame to ensure DOM is ready\n requestAnimationFrame(() => {\n htmlElement.focus();\n this.logger.debug(\"Autofocused element:\", htmlElement.tagName, htmlElement.id || htmlElement.getAttribute(\"name\"));\n });\n } else if (!isVisible && wasFocused) {\n // Reset the flag when element becomes hidden so it can be refocused when shown again\n htmlElement.removeAttribute(\"data-lvt-autofocused\");\n }\n });\n };\n\n // Process autofocus immediately for any existing elements\n processAutofocus();\n\n // Set up MutationObserver to watch for new autofocus elements or visibility changes\n const observer = new MutationObserver((mutations) => {\n let shouldProcess = false;\n\n mutations.forEach((mutation) => {\n // Check for added nodes\n if (mutation.type === \"childList\" && mutation.addedNodes.length > 0) {\n mutation.addedNodes.forEach((node) => {\n if (node instanceof Element) {\n if (node.hasAttribute(\"lvt-autofocus\") || node.querySelector(\"[lvt-autofocus]\")) {\n shouldProcess = true;\n }\n }\n });\n }\n\n // Check for attribute changes that might affect visibility\n // We intentionally check \"class\" changes to handle CSS-based visibility\n // (e.g., Tailwind's \"hidden\" class, Bootstrap's \"d-none\", etc.)\n // This may cause some extra processing but ensures visibility changes\n // via class toggles are detected.\n if (mutation.type === \"attributes\") {\n const target = mutation.target as Element;\n if (target.hasAttribute(\"lvt-autofocus\") ||\n mutation.attributeName === \"hidden\" ||\n mutation.attributeName === \"style\" ||\n mutation.attributeName === \"class\") {\n shouldProcess = true;\n }\n }\n });\n\n if (shouldProcess) {\n processAutofocus();\n }\n });\n\n observer.observe(wrapperElement, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: [\"hidden\", \"style\", \"class\", \"lvt-autofocus\"]\n });\n\n (wrapperElement as any)[observerKey] = observer;\n this.logger.debug(\"Autofocus delegation set up\");\n }\n\n}\n", "import type { Logger } from \"../utils/logger\";\n\nexport interface LinkInterceptorContext {\n getWrapperElement(): Element | null;\n handleNavigationResponse(html: string): void;\n // Send an in-band navigate message over the existing WebSocket.\n // Returns true if the message was sent, false if it was dropped\n // (e.g. WS not open). The caller uses this to decide whether to push\n // browser history state \u2014 only advancing the URL when the server will\n // actually receive the navigate eliminates the TOCTOU window where\n // the WS could close between canSendNavigate() and the actual send.\n sendNavigate(href: string): boolean;\n // Returns true when an in-band navigate message can be sent (i.e.\n // WebSocket mode is active and the socket is OPEN). In HTTP mode or\n // when the WS is not yet open, this is false and the same-pathname\n // fast path must fall through to a normal fetch.\n canSendNavigate(): boolean;\n}\n\n/**\n * Intercepts <a> clicks within the LiveTemplate wrapper for SPA navigation.\n *\n * - Same pathname (query-string change only) -> sends __navigate__ over WS;\n * no fetch, no DOM replace, no reconnect.\n * - Different pathname (cross-handler or just different route) -> fetches\n * new HTML and hands it to handleNavigationResponse, which decides\n * between same-handler DOM replace and cross-handler reconnect.\n * - External links, target=\"_blank\", download, and lvt-nav:no-intercept\n * are skipped.\n *\n * Uses AbortController to cancel in-flight fetches when a new navigation\n * starts (rapid clicks, back/forward during fetch).\n */\nexport class LinkInterceptor {\n private popstateListener: (() => void) | null = null;\n private abortController: AbortController | null = null;\n // Tracks the URL that was last successfully navigated to (or the initial\n // page URL). Updated after each in-band __navigate__ push and after each\n // fetch-based navigation. The popstate handler uses this to compare the\n // target URL against the URL we were actually at *before* the browser\n // changed window.location, because by the time popstate fires, the browser\n // has already moved window.location to the target \u2014 making a naive\n // window.location comparison always look like a same-URL no-op.\n private currentHref: string = window.location.href;\n\n constructor(\n private readonly context: LinkInterceptorContext,\n private readonly logger: Logger\n ) {}\n\n /**\n * Remove the click listener registered by setup() for a specific\n * wrapper ID. Call this before cross-handler navigation changes the\n * wrapper's data-lvt-id, to prevent orphaned listeners.\n *\n * Also aborts any in-flight navigate() fetch so it cannot call\n * handleNavigationResponse after teardown and trigger a duplicate\n * or out-of-date navigation.\n */\n teardownForWrapper(wrapperId: string | null): void {\n // Abort any in-flight fetch \u2014 whether or not a wrapper ID is passed.\n // The caller may be tearing down before a cross-handler transition,\n // and we don't want a pending fetch to land post-teardown.\n this.abortController?.abort();\n this.abortController = null;\n\n if (!wrapperId) return;\n const listenerKey = `__lvt_link_intercept_${wrapperId}`;\n const existing = (document as any)[listenerKey];\n if (existing) {\n // Explicit capture flag (false) for consistency with\n // EventDelegator.teardownForWrapper \u2014 defaults match but\n // explicit is clearer.\n document.removeEventListener(\"click\", existing, false);\n delete (document as any)[listenerKey];\n }\n }\n\n setup(wrapper: Element): void {\n // Refresh currentHref so the popstate handler compares against the URL\n // that is actually showing when this setup runs, not a stale value from\n // a previous navigation or from construction time (which may predate the\n // first history.replaceState in tests and cross-handler nav re-setups).\n this.currentHref = window.location.href;\n\n const wrapperId = wrapper.getAttribute(\"data-lvt-id\");\n const listenerKey = `__lvt_link_intercept_${wrapperId}`;\n const existing = (document as any)[listenerKey];\n if (existing) {\n document.removeEventListener(\"click\", existing);\n }\n\n const listener = (e: Event) => {\n const target = (e.target as Element)?.closest(\"a[href]\") as HTMLAnchorElement | null;\n if (!target) return;\n\n const currentWrapper = this.context.getWrapperElement();\n if (!currentWrapper || !currentWrapper.contains(target)) return;\n\n if (this.shouldSkip(target)) return;\n\n e.preventDefault();\n this.navigate(target.href);\n };\n\n document.addEventListener(\"click\", listener);\n (document as any)[listenerKey] = listener;\n\n // Handle back/forward navigation\n if (!this.popstateListener) {\n this.popstateListener = () => {\n // Capture the URL we were at *before* the browser moved to the new\n // history entry. This lets navigate() compare pathname+search against\n // the previous URL rather than window.location (which already reflects\n // the target after popstate fires).\n const prevHref = this.currentHref;\n this.currentHref = window.location.href;\n this.navigate(window.location.href, false, prevHref);\n };\n window.addEventListener(\"popstate\", this.popstateListener);\n }\n }\n\n private shouldSkip(link: HTMLAnchorElement): boolean {\n // External links\n if (link.origin !== window.location.origin) return true;\n // target=\"_blank\" or other targets\n if (link.target && link.target !== \"_self\") return true;\n // Download links\n if (link.hasAttribute(\"download\")) return true;\n // Opt-out attribute for link interception\n if (link.hasAttribute(\"lvt-nav:no-intercept\")) return true;\n // Hash-only links (scroll anchors)\n if (link.pathname === window.location.pathname && link.hash) return true;\n // mailto/tel/javascript\n const protocol = link.protocol;\n if (protocol !== \"http:\" && protocol !== \"https:\") return true;\n\n return false;\n }\n\n // prevHref is the URL the client was at *before* this navigation.\n // For link clicks (pushState=true) it defaults to window.location.href,\n // which is correct because pushState hasn't run yet. For popstate\n // (pushState=false) the popstate listener supplies the saved currentHref\n // so the same-pathname comparison reflects the real previous entry, not\n // window.location (which the browser already updated to the target).\n private async navigate(\n href: string,\n pushState: boolean = true,\n prevHref: string = window.location.href\n ): Promise<void> {\n const targetURL = new URL(href, window.location.origin);\n const refURL = new URL(prevHref, window.location.origin);\n const samePath =\n targetURL.origin === refURL.origin &&\n targetURL.pathname === refURL.pathname;\n\n if (samePath) {\n const sameSearch = targetURL.search === refURL.search;\n if (sameSearch) {\n // Hash-only change or exact same URL \u2014 the browser handles scroll\n // to the anchor; no server round-trip is needed. This also correctly\n // handles popstate for hash-only back/forward because the popstate\n // listener passes prevHref (the previous entry), so refURL reflects\n // where we came from rather than the already-updated window.location.\n //\n // Still abort any in-flight cross-path fetch: if a fetch was in\n // progress when the user clicked a hash anchor, we don't want it\n // to resolve and call handleNavigationResponse unexpectedly.\n this.abortController?.abort();\n this.abortController = null;\n return;\n }\n\n // __navigate__ fast path: same pathname, different search, WS mode.\n // Only for explicit forward navigation (pushState=true / link clicks).\n // For popstate (pushState=false) the search difference is real (it\n // compares against the previous entry via prevHref), but back/forward\n // must restore prior page state via a full fetch, not a WS message\n // that only forwards query data to Mount.\n if (pushState && this.context.canSendNavigate()) {\n // Abort any in-flight fetch even on the fast path: a user could\n // click a cross-path link (starting a fetch) and quickly click a\n // same-pathname link. Without aborting, the earlier fetch can\n // still resolve and call handleNavigationResponse, racing with the\n // in-band __navigate__ update.\n this.abortController?.abort();\n this.abortController = null;\n // sendNavigate returns true if the WS message was actually sent.\n // Push history state ONLY on success to keep window.location\n // consistent with what the server received.\n // If sent === false (defensive path \u2014 normally unreachable since\n // canSendNavigate() already checked readyState), fall through to\n // the normal fetch so the navigation isn't silently dropped.\n const sent = this.context.sendNavigate(href);\n if (sent) {\n window.history.pushState(null, \"\", href);\n this.currentHref = href;\n return;\n }\n // sendNavigate returned false \u2014 fall through to fetch as recovery.\n }\n // HTTP mode, WS not OPEN, sendNavigate returned false, or popstate:\n // fall through to normal fetch. pushState is handled downstream.\n }\n\n // Cancel any in-flight navigation fetch\n this.abortController?.abort();\n this.abortController = new AbortController();\n\n try {\n const response = await fetch(href, {\n credentials: \"include\",\n headers: { Accept: \"text/html\" },\n signal: this.abortController.signal,\n });\n\n if (!response.ok) {\n window.location.href = href;\n return;\n }\n\n const html = await response.text();\n\n // Push state BEFORE handling response so that cross-handler\n // navigation reconnects the WebSocket to the correct URL.\n // connect() derives the WebSocket path from window.location.\n if (pushState) {\n window.history.pushState(null, \"\", href);\n }\n\n this.currentHref = href;\n this.context.handleNavigationResponse(html);\n } catch (e: unknown) {\n // AbortError means a new navigation superseded this one \u2014 ignore\n if (e instanceof DOMException && e.name === \"AbortError\") return;\n window.location.href = href;\n }\n }\n}\n", "import type { Logger } from \"../utils/logger\";\n\nexport interface ObserverContext {\n getWrapperElement(): Element | null;\n send(message: any): void;\n}\n\n/**\n * Manages LiveTemplate observers such as infinite scroll and DOM mutations.\n */\nexport class ObserverManager {\n private infiniteScrollObserver: IntersectionObserver | null = null;\n private mutationObserver: MutationObserver | null = null;\n private observedSentinel: Element | null = null;\n private updatedListener: ((e: Event) => void) | null = null;\n private updatedListenerWrapper: Element | null = null;\n\n // Throttles infinite-scroll dispatches: one in-flight load_more at a time.\n // Without this, rapid observer re-fires stack concurrent actions and the\n // server's per-response diffs compose into duplicate rows on the client.\n //\n // Cleared precisely when the server confirms the load_more response has\n // been applied (via the `lvt:updated` event with `action === \"load_more\"`),\n // NOT on every DOM mutation \u2014 an unrelated mutation (e.g. a flash message\n // toggling, a highlight flashing) between the dispatch and the response\n // would otherwise clear the flag early and allow a concurrent send.\n private loadMorePending = false;\n // Safety net: if the server never responds with an lvt:updated event\n // for load_more (network error, server-side exception, server just\n // dropped the message), the flag above would stay true forever and\n // infinite scroll would be silently deadlocked until page reload.\n // This timeout releases the throttle after 30s so the next sentinel\n // intersection can re-trigger the load.\n private loadMoreTimeoutId: number | null = null;\n private static readonly LOAD_MORE_TIMEOUT_MS = 30000;\n\n constructor(\n private readonly context: ObserverContext,\n private readonly logger: Logger\n ) {}\n\n setupInfiniteScrollObserver(): void {\n const wrapperElement = this.context.getWrapperElement();\n if (!wrapperElement) return;\n\n // Attach the lvt:updated listener once per wrapper. The event fires\n // after every tree update carrying the dispatched action's name in\n // its detail; we use action === \"load_more\" as the precise signal\n // that the throttle can be lifted.\n this.ensureUpdatedListener(wrapperElement);\n\n const sentinel = document.getElementById(\"scroll-sentinel\");\n if (!sentinel) {\n // Sentinel removed (HasMore flipped false): release the old observer\n // AND clear any in-flight load_more throttle. Without releaseLoadMore\n // here, a HasMore flip-flop (server removes sentinel, then restores\n // it moments later without dispatching an lvt:updated action=load_more\n // in between) would leave loadMorePending=true with a 30s safety\n // timer armed \u2014 silently dropping the next intersection until the\n // timer fires.\n if (this.infiniteScrollObserver) {\n this.infiniteScrollObserver.disconnect();\n this.infiniteScrollObserver = null;\n this.observedSentinel = null;\n }\n this.releaseLoadMore();\n return;\n }\n\n // Reuse the existing observer when the sentinel node is the same \u2014\n // avoids allocating a fresh IntersectionObserver per DOM mutation.\n if (this.infiniteScrollObserver && this.observedSentinel === sentinel) {\n return;\n }\n\n if (this.infiniteScrollObserver) {\n this.infiniteScrollObserver.disconnect();\n }\n\n this.infiniteScrollObserver = new IntersectionObserver(\n (entries) => {\n if (!entries[0].isIntersecting) return;\n if (this.loadMorePending) {\n this.logger.debug(\"Sentinel visible but load_more already pending, skipping\");\n return;\n }\n this.loadMorePending = true;\n this.armLoadMoreTimeout();\n this.logger.debug(\"Sentinel visible, sending load_more action\");\n this.context.send({ action: \"load_more\" });\n },\n {\n rootMargin: \"200px\",\n }\n );\n\n this.infiniteScrollObserver.observe(sentinel);\n this.observedSentinel = sentinel;\n this.logger.debug(\"Observer set up successfully\");\n }\n\n private ensureUpdatedListener(wrapper: Element): void {\n if (this.updatedListener && this.updatedListenerWrapper === wrapper) return;\n // Detach any listener from the previous wrapper (e.g. after cross-\n // handler navigation swaps the wrapper element).\n if (this.updatedListener && this.updatedListenerWrapper) {\n this.updatedListenerWrapper.removeEventListener(\"lvt:updated\", this.updatedListener);\n }\n this.updatedListener = (e: Event) => {\n const detail = (e as CustomEvent).detail;\n if (detail?.action !== \"load_more\") return;\n this.releaseLoadMore();\n // Force a fresh IntersectionObserver so its immediate callback fires\n // with the post-mutation intersection state \u2014 lets the auto-advance\n // cascade continue if the sentinel is still visible after the new\n // rows are appended.\n this.observedSentinel = null;\n this.setupInfiniteScrollObserver();\n };\n wrapper.addEventListener(\"lvt:updated\", this.updatedListener);\n this.updatedListenerWrapper = wrapper;\n }\n\n private armLoadMoreTimeout(): void {\n this.clearLoadMoreTimeout();\n this.loadMoreTimeoutId = window.setTimeout(() => {\n this.logger.warn(\n `load_more response not received within ${ObserverManager.LOAD_MORE_TIMEOUT_MS}ms; releasing throttle`\n );\n this.loadMoreTimeoutId = null;\n this.loadMorePending = false;\n // Force the next intersection to re-trigger by rebuilding the\n // observer, since the sentinel may still be visible.\n this.observedSentinel = null;\n this.setupInfiniteScrollObserver();\n }, ObserverManager.LOAD_MORE_TIMEOUT_MS);\n }\n\n private releaseLoadMore(): void {\n this.loadMorePending = false;\n this.clearLoadMoreTimeout();\n }\n\n private clearLoadMoreTimeout(): void {\n if (this.loadMoreTimeoutId !== null) {\n clearTimeout(this.loadMoreTimeoutId);\n this.loadMoreTimeoutId = null;\n }\n }\n\n setupInfiniteScrollMutationObserver(): void {\n const wrapperElement = this.context.getWrapperElement();\n if (!wrapperElement) return;\n\n if (this.mutationObserver) {\n this.mutationObserver.disconnect();\n }\n\n // The mutation observer catches structural changes that replace the\n // sentinel DOM node (e.g. morphdom recreating it on a page-level\n // restructure). setupInfiniteScrollObserver's identity check makes\n // the common case \u2014 same sentinel, new mutation \u2014 a cheap no-op.\n this.mutationObserver = new MutationObserver(() => {\n this.setupInfiniteScrollObserver();\n });\n\n this.mutationObserver.observe(wrapperElement, {\n childList: true,\n subtree: true,\n });\n\n this.logger.debug(\"MutationObserver set up successfully\");\n }\n\n teardown(): void {\n if (this.infiniteScrollObserver) {\n this.infiniteScrollObserver.disconnect();\n this.infiniteScrollObserver = null;\n }\n if (this.mutationObserver) {\n this.mutationObserver.disconnect();\n this.mutationObserver = null;\n }\n if (this.updatedListener && this.updatedListenerWrapper) {\n this.updatedListenerWrapper.removeEventListener(\"lvt:updated\", this.updatedListener);\n }\n this.updatedListener = null;\n this.updatedListenerWrapper = null;\n this.observedSentinel = null;\n this.releaseLoadMore();\n }\n}\n", "/**\n * Handles showing and hiding the global LiveTemplate loading indicator.\n */\nexport class LoadingIndicator {\n private bar: HTMLElement | null = null;\n\n show(): void {\n if (this.bar) return;\n\n const bar = document.createElement(\"div\");\n bar.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: 3px;\n background: linear-gradient(90deg, #3b82f6 0%, #60a5fa 50%, #3b82f6 100%);\n background-size: 200% 100%;\n z-index: 9999;\n animation: lvt-loading-shimmer 1.5s ease-in-out infinite;\n `;\n\n if (!document.getElementById(\"lvt-loading-styles\")) {\n const style = document.createElement(\"style\");\n style.id = \"lvt-loading-styles\";\n style.textContent = `\n @keyframes lvt-loading-shimmer {\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n }\n `;\n document.head.appendChild(style);\n }\n\n document.body.insertBefore(bar, document.body.firstChild);\n this.bar = bar;\n }\n\n hide(): void {\n if (!this.bar) return;\n\n if (this.bar.parentNode) {\n this.bar.parentNode.removeChild(this.bar);\n }\n this.bar = null;\n }\n}\n", "/**\n * Enables and disables all form controls inside the LiveTemplate wrapper.\n */\nexport class FormDisabler {\n disable(wrapper: Element | null): void {\n if (!wrapper) return;\n\n const forms = wrapper.querySelectorAll(\"form\");\n forms.forEach((form) => {\n const inputs = form.querySelectorAll(\"input, textarea, select, button\");\n inputs.forEach((input) => {\n (input as HTMLInputElement).disabled = true;\n });\n });\n }\n\n enable(wrapper: Element | null): void {\n if (!wrapper) return;\n\n const forms = wrapper.querySelectorAll(\"form\");\n forms.forEach((form) => {\n const inputs = form.querySelectorAll(\"input, textarea, select, button\");\n inputs.forEach((input) => {\n (input as HTMLInputElement).disabled = false;\n });\n });\n }\n}\n", "/**\n * Polyfill for the HTML Invoker Commands API (command/commandfor).\n * Enables <button command=\"show-modal\" commandfor=\"dialog-id\"> to work\n * cross-browser by calling .showModal()/.close() on the target <dialog>.\n *\n * No-op when native support is detected.\n * Spec: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/command\n */\n\nlet installed = false;\n\nfunction handleClick(e: Event): void {\n const el = e.target;\n if (!el || !(el instanceof Element)) return;\n\n const button = el.closest(\"button[commandfor]\") as HTMLButtonElement | null;\n if (!button || button.disabled) return;\n\n const targetId = button.getAttribute(\"commandfor\");\n if (!targetId) return;\n\n const command = button.getAttribute(\"command\");\n const target = document.getElementById(targetId);\n if (!target || !(target instanceof HTMLDialogElement)) return;\n\n if (command === \"show-modal\" && !target.open) {\n target.showModal();\n } else if (command === \"close\" && target.open) {\n target.close();\n }\n}\n\nexport function setupInvokerPolyfill(): void {\n if (\"commandForElement\" in HTMLButtonElement.prototype) return;\n if (installed) return;\n\n installed = true;\n document.addEventListener(\"click\", handleClick);\n}\n\n/** @internal Reset polyfill state. Test-only \u2014 do not call from production code. */\nexport function teardownInvokerPolyfill(): void {\n if (installed) {\n document.removeEventListener(\"click\", handleClick);\n installed = false;\n }\n}\n", "import type { TreeNode, UpdateResult } from \"../types\";\nimport type { Logger } from \"../utils/logger\";\n\ninterface RangeStateEntry {\n items: any[];\n statics: any[];\n staticsMap?: Record<string, string[]>;\n}\n\n/**\n * Deep clone an object. Uses structuredClone if available (Node 17+, modern browsers),\n * falls back to JSON.parse/stringify for older environments.\n */\nfunction deepClone<T>(obj: T): T {\n if (typeof structuredClone === \"function\") {\n return structuredClone(obj);\n }\n return JSON.parse(JSON.stringify(obj));\n}\n\n/**\n * Checks if a node is a valid range structure.\n *\n * A \"range\" in LiveTemplate represents a {{range .Items}}...{{end}} construct.\n * It has:\n * - `d` (dynamics): Array of rendered items\n * - `s` (statics): Array of static HTML fragments between dynamic slots\n *\n * A \"non-range\" is any other tree node (e.g., an {{else}} clause with simple content).\n *\n * @param node - The tree node to check\n * @returns true if the node has both `d` and `s` arrays (valid range structure)\n */\nfunction isRangeNode(node: any): boolean {\n return (\n node != null &&\n typeof node === \"object\" &&\n Array.isArray(node.d) &&\n Array.isArray(node.s)\n );\n}\n\n/**\n * Checks if a node or any of its nested children contains a range structure.\n *\n * This is needed for detecting structure transitions like:\n * - {{if .ShowList}}<ul>{{range .Items}}...{{end}}</ul>{{else}}<p>Hidden</p>{{end}}\n *\n * When ShowList changes from true to false, the outer node doesn't have `d` directly,\n * but its child at position \"0\" does. We need to detect this nested range to know\n * that a full replacement is required instead of a merge.\n *\n * @param node - The tree node to check\n * @returns true if this node or any nested child has a range structure\n */\nfunction hasRangeAnywhere(node: any, depth = 0): boolean {\n // Depth limit to prevent potential stack overflow on deeply nested or malformed trees\n const MAX_DEPTH = 50;\n if (depth > MAX_DEPTH || node == null || typeof node !== \"object\" || Array.isArray(node)) {\n return false;\n }\n\n // Check if this node itself is a range\n if (isRangeNode(node)) {\n return true;\n }\n\n // Check numeric keys (dynamic positions) for nested ranges\n for (const key of Object.keys(node)) {\n if (/^\\d+$/.test(key)) {\n const child = node[key];\n if (child != null && typeof child === \"object\" && !Array.isArray(child)) {\n if (hasRangeAnywhere(child, depth + 1)) {\n return true;\n }\n }\n }\n }\n\n return false;\n}\n\n/**\n * Determines if a structure transition requires full replacement instead of merge.\n *\n * This handles cases where:\n * 1. Old structure has a range (directly or nested) but new structure doesn't\n * 2. New structure has statics (indicating a complete structure definition)\n * 3. Old structure has dynamics that new structure doesn't have\n *\n * @param existing - The existing tree node\n * @param update - The update tree node\n * @returns true if the update should fully replace existing instead of merging\n */\nfunction shouldFullReplace(existing: any, update: any): boolean {\n // If update doesn't have statics, it's a partial update, not a replacement\n if (!update.s || !Array.isArray(update.s)) {\n return false;\n }\n\n // Check for range\u2192non-range transition (including nested ranges)\n if (hasRangeAnywhere(existing) && !hasRangeAnywhere(update)) {\n return true;\n }\n\n // Check if existing has dynamics that update doesn't have\n for (const key of Object.keys(existing)) {\n if (/^\\d+$/.test(key) && !(key in update)) {\n // Existing has a dynamic position that update doesn't\n // If update has statics, this is a structure change\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Handles tree state management and HTML reconstruction logic for LiveTemplate.\n */\nexport class TreeRenderer {\n private treeState: TreeNode = {};\n private rangeState: Record<string, RangeStateEntry> = {};\n private rangeIdKeys: Record<string, string> = {};\n\n constructor(private readonly logger: Logger) {}\n\n applyUpdate(update: TreeNode): UpdateResult {\n let changed = false;\n\n for (const [key, value] of Object.entries(update)) {\n const isDifferentialOps =\n Array.isArray(value) &&\n value.length > 0 &&\n Array.isArray(value[0]) &&\n typeof value[0][0] === \"string\";\n\n if (isDifferentialOps) {\n // Check if there's an existing range structure to apply operations to\n const existing = this.treeState[key];\n const existingIsRange =\n existing &&\n typeof existing === \"object\" &&\n !Array.isArray(existing) &&\n Array.isArray(existing.d) &&\n Array.isArray(existing.s);\n\n if (existingIsRange) {\n // Apply differential operations to existing range structure\n this.treeState[key] = deepClone(existing);\n this.applyDifferentialOpsToRange(this.treeState[key], value, key);\n } else {\n // No existing range, store operations directly (will use rangeState later)\n this.treeState[key] = value;\n }\n changed = true;\n } else {\n const oldValue = this.treeState[key];\n const newValue =\n typeof value === \"object\" && value !== null && !Array.isArray(value)\n ? this.deepMergeTreeNodes(oldValue, value, key)\n : value;\n\n if (JSON.stringify(oldValue) !== JSON.stringify(newValue)) {\n this.treeState[key] = newValue;\n changed = true;\n }\n }\n }\n\n const html = this.reconstructFromTree(this.treeState, \"\");\n return { html, changed };\n }\n\n reset(): void {\n this.treeState = {};\n this.rangeState = {};\n this.rangeIdKeys = {};\n }\n\n getTreeState(): TreeNode {\n return { ...this.treeState };\n }\n\n getStaticStructure(): string[] | null {\n return this.treeState.s || null;\n }\n\n private deepMergeTreeNodes(\n existing: any,\n update: any,\n currentPath: string = \"\"\n ): any {\n if (\n typeof update !== \"object\" ||\n update === null ||\n Array.isArray(update)\n ) {\n return update;\n }\n\n if (\n typeof existing !== \"object\" ||\n existing === null ||\n Array.isArray(existing)\n ) {\n return update;\n }\n\n // Detect structure transitions that require full replacement instead of merge.\n // This handles:\n // 1. Direct range\u2192non-range: existing has d/s arrays, update doesn't\n // 2. Nested range\u2192non-range: existing has range in a child, update doesn't\n // 3. Structure changes: existing has dynamics that update doesn't have\n //\n // Without this check, old range data would be preserved and mixed with\n // new statics, causing wrong content or memory leaks.\n if (shouldFullReplace(existing, update)) {\n this.logger.debug(\n `[deepMerge] Structure transition at path ${currentPath}, replacing instead of merging`\n );\n return update;\n }\n\n const merged: any = { ...existing };\n\n for (const [key, value] of Object.entries(update)) {\n const fieldPath = currentPath ? `${currentPath}.${key}` : key;\n\n // Check if value is a differential operations array\n const isDifferentialOps =\n Array.isArray(value) &&\n value.length > 0 &&\n Array.isArray(value[0]) &&\n typeof value[0][0] === \"string\";\n\n // Check if existing value is a range structure\n const existingIsRange =\n merged[key] &&\n typeof merged[key] === \"object\" &&\n !Array.isArray(merged[key]) &&\n Array.isArray(merged[key].d) &&\n Array.isArray(merged[key].s);\n\n if (isDifferentialOps && existingIsRange) {\n // Deep clone the range structure before modifying to avoid mutating the original\n // (shallow copy {...existing} keeps shared references to nested objects)\n merged[key] = deepClone(merged[key]);\n // Apply differential operations to the cloned range\n this.logger.debug(\n `[deepMerge] Applying diff ops at path ${fieldPath}`,\n { ops: value, rangeItems: merged[key].d?.length }\n );\n this.applyDifferentialOpsToRange(merged[key], value, fieldPath);\n } else if (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n typeof merged[key] === \"object\" &&\n merged[key] !== null &&\n !Array.isArray(merged[key])\n ) {\n merged[key] = this.deepMergeTreeNodes(merged[key], value, fieldPath);\n } else {\n merged[key] = value;\n }\n }\n\n return merged;\n }\n\n /**\n * Applies differential operations to the provided range structure in-place.\n * The caller is responsible for passing the object to be mutated (typically a clone).\n * This is called when merging nested updates that contain range operations.\n */\n private applyDifferentialOpsToRange(\n rangeStructure: any,\n operations: any[],\n statePath: string\n ): void {\n // Validate rangeStructure before proceeding\n if (\n !rangeStructure ||\n typeof rangeStructure !== \"object\" ||\n !Array.isArray(rangeStructure.d) ||\n !Array.isArray(rangeStructure.s)\n ) {\n this.logger.error(\n `[applyDiffOpsToRange] Invalid rangeStructure at path ${statePath}`,\n { rangeStructure }\n );\n return;\n }\n\n const currentItems = rangeStructure.d;\n\n // Ensure rangeState is synchronized\n if (!this.rangeState[statePath]) {\n this.rangeState[statePath] = {\n items: currentItems,\n statics: rangeStructure.s,\n staticsMap: rangeStructure.sm,\n };\n }\n // Also check for idKey metadata\n if (\n rangeStructure.m &&\n typeof rangeStructure.m === \"object\" &&\n typeof rangeStructure.m.idKey === \"string\"\n ) {\n this.rangeIdKeys[statePath] = rangeStructure.m.idKey;\n }\n\n this.logger.debug(\n `[applyDiffOpsToRange] path=${statePath}, idKey=${this.rangeIdKeys[statePath]}, items=${currentItems.length}, ops=${operations.length}`\n );\n\n for (const operation of operations) {\n if (!Array.isArray(operation) || operation.length < 2) {\n continue;\n }\n\n const opType = operation[0];\n\n switch (opType) {\n case \"r\": {\n const key = operation[1];\n const removeIndex = this.findItemIndexByKey(\n currentItems,\n key,\n rangeStructure.s,\n statePath\n );\n this.logger.debug(\n `[applyDiffOpsToRange] Remove: key=${key}, index=${removeIndex}, total=${currentItems.length}`\n );\n if (removeIndex >= 0) {\n currentItems.splice(removeIndex, 1);\n this.logger.debug(`[applyDiffOpsToRange] After removal: ${currentItems.length} items`);\n } else {\n this.logger.debug(`[applyDiffOpsToRange] Remove failed: key ${key} not found`);\n }\n break;\n }\n case \"u\": {\n const updateIndex = this.findItemIndexByKey(\n currentItems,\n operation[1],\n rangeStructure.s,\n statePath\n );\n const changes = operation[2];\n if (updateIndex >= 0 && changes) {\n currentItems[updateIndex] = this.mergeRangeItem(\n currentItems[updateIndex],\n changes,\n statePath\n );\n }\n break;\n }\n case \"a\": {\n const itemsToAdd = Array.isArray(operation[1])\n ? operation[1]\n : [operation[1]];\n if (operation[2]) {\n rangeStructure.s = operation[2];\n }\n currentItems.push(...itemsToAdd);\n if (\n operation[3] &&\n typeof operation[3] === \"object\" &&\n operation[3].idKey\n ) {\n this.rangeIdKeys[statePath] = operation[3].idKey;\n }\n break;\n }\n case \"p\": {\n const itemsToPrepend = Array.isArray(operation[1])\n ? operation[1]\n : [operation[1]];\n if (operation[2]) {\n rangeStructure.s = operation[2];\n }\n currentItems.unshift(...itemsToPrepend);\n break;\n }\n case \"i\": {\n const targetIndex = this.findItemIndexByKey(\n currentItems,\n operation[1],\n rangeStructure.s,\n statePath\n );\n if (targetIndex >= 0) {\n const itemsToInsert = Array.isArray(operation[2])\n ? operation[2]\n : [operation[2]];\n currentItems.splice(targetIndex + 1, 0, ...itemsToInsert);\n }\n break;\n }\n case \"o\": {\n const newOrder = operation[1] as string[];\n const reorderedItems: any[] = [];\n const itemsByKey = new Map<string, any>();\n\n for (const item of currentItems) {\n const itemKey = this.getItemKey(item, rangeStructure.s, statePath);\n if (itemKey) {\n itemsByKey.set(itemKey, item);\n }\n }\n\n for (const orderedKey of newOrder) {\n const item = itemsByKey.get(orderedKey);\n if (item) {\n reorderedItems.push(item);\n }\n }\n\n currentItems.length = 0;\n currentItems.push(...reorderedItems);\n break;\n }\n default:\n break;\n }\n }\n\n // Update rangeState to reflect the changes\n this.rangeState[statePath] = {\n items: currentItems,\n statics: rangeStructure.s,\n staticsMap: rangeStructure.sm,\n };\n }\n\n private reconstructFromTree(node: TreeNode, statePath: string): string {\n if (node.s && Array.isArray(node.s)) {\n let html = \"\";\n\n for (let i = 0; i < node.s.length; i++) {\n const staticSegment = node.s[i];\n html += staticSegment;\n\n if (i < node.s.length - 1) {\n const dynamicKey = i.toString();\n if (node[dynamicKey] !== undefined) {\n const newStatePath = statePath\n ? `${statePath}.${dynamicKey}`\n : dynamicKey;\n html += this.renderValue(\n node[dynamicKey],\n dynamicKey,\n newStatePath\n );\n }\n }\n }\n\n html = html.replace(/<root>/g, \"\").replace(/<\\/root>/g, \"\");\n return html;\n }\n\n return this.renderValue(node, \"\", statePath);\n }\n\n private renderValue(\n value: any,\n fieldKey?: string,\n statePath?: string\n ): string {\n if (value === null || value === undefined) {\n return \"\";\n }\n\n if (\n typeof value === \"string\" &&\n value.startsWith(\"{{\") &&\n value.endsWith(\"}}\")\n ) {\n return \"\";\n }\n\n if (typeof value === \"object\" && !Array.isArray(value)) {\n if (\n value.d &&\n Array.isArray(value.d) &&\n value.s &&\n Array.isArray(value.s)\n ) {\n const stateKey = statePath || fieldKey || \"\";\n if (stateKey) {\n this.rangeState[stateKey] = {\n items: value.d,\n statics: value.s,\n staticsMap: value.sm,\n };\n if (\n value.m &&\n typeof value.m === \"object\" &&\n typeof value.m.idKey === \"string\"\n ) {\n this.rangeIdKeys[stateKey] = value.m.idKey;\n }\n }\n return this.renderRangeStructure(value, fieldKey, statePath);\n }\n\n if (\"s\" in value && Array.isArray((value as TreeNode).s)) {\n return this.reconstructFromTree(value as TreeNode, statePath || \"\");\n }\n\n // Handle objects with only numeric keys (dynamics without statics)\n // This occurs when server sends partial updates for nested TreeNodes\n const keys = Object.keys(value);\n const numericKeys = keys.filter((k) => /^\\d+$/.test(k)).sort((a, b) => parseInt(a) - parseInt(b));\n if (numericKeys.length > 0 && numericKeys.length === keys.length) {\n // All keys are numeric - render each dynamic value in order\n return numericKeys\n .map((k) => {\n const itemStatePath = statePath ? `${statePath}.${k}` : k;\n return this.renderValue((value as Record<string, unknown>)[k], k, itemStatePath);\n })\n .join(\"\");\n }\n }\n\n if (Array.isArray(value)) {\n if (\n value.length > 0 &&\n Array.isArray(value[0]) &&\n typeof value[0][0] === \"string\"\n ) {\n return this.applyDifferentialOperations(value, statePath);\n }\n\n return value\n .map((item, idx) => {\n const itemKey = idx.toString();\n const itemStatePath = statePath ? `${statePath}.${itemKey}` : itemKey;\n if (typeof item === \"object\" && item && (item as TreeNode).s) {\n return this.reconstructFromTree(item as TreeNode, itemStatePath);\n }\n return this.renderValue(item, itemKey, itemStatePath);\n })\n .join(\"\");\n }\n\n if (typeof value === \"object\") {\n // Plain data objects (without tree structure) are state values that shouldn't be rendered.\n // This happens when state contains objects like EditingItem that are used by server-side\n // templates but aren't meant to be rendered directly in the DOM.\n // Skip them silently instead of converting to \"[object Object]\".\n this.logger.debug(\n \"Skipping plain object value (not a tree node) - this is normal for state-only data\"\n );\n return \"\";\n }\n\n return String(value);\n }\n\n private renderRangeStructure(\n rangeNode: any,\n fieldKey?: string,\n statePath?: string\n ): string {\n const { d: dynamics, s: statics, sm: staticsMap } = rangeNode;\n\n if (!dynamics || !Array.isArray(dynamics)) {\n return \"\";\n }\n\n if (dynamics.length === 0) {\n if (rangeNode[\"else\"]) {\n const elseKey = \"else\";\n const elseStatePath = statePath ? `${statePath}.else` : \"else\";\n return this.renderValue(rangeNode[\"else\"], elseKey, elseStatePath);\n }\n return \"\";\n }\n\n // Check if we have per-item statics via StaticsMap\n const hasStaticsMap = staticsMap && typeof staticsMap === \"object\";\n\n if (statics && Array.isArray(statics)) {\n return dynamics\n .map((item: any, itemIdx: number) => {\n // Get per-item statics from StaticsMap if available, otherwise use shared statics\n let itemStatics = statics;\n if (hasStaticsMap && item._sk && staticsMap[item._sk]) {\n itemStatics = staticsMap[item._sk];\n }\n\n let html = \"\";\n\n for (let i = 0; i < itemStatics.length; i++) {\n html += itemStatics[i];\n\n if (i < itemStatics.length - 1) {\n const localKey = i.toString();\n if (item[localKey] !== undefined) {\n const itemStatePath = statePath\n ? `${statePath}.${itemIdx}.${localKey}`\n : `${itemIdx}.${localKey}`;\n html += this.renderValue(\n item[localKey],\n localKey,\n itemStatePath\n );\n }\n }\n }\n\n return html;\n })\n .join(\"\");\n }\n\n return dynamics\n .map((item: any, idx: number) => {\n const itemKey = idx.toString();\n const itemStatePath = statePath ? `${statePath}.${itemKey}` : itemKey;\n return this.renderValue(item, itemKey, itemStatePath);\n })\n .join(\"\");\n }\n\n private applyDifferentialOperations(\n operations: any[],\n statePath?: string\n ): string {\n if (!statePath || !this.rangeState[statePath]) {\n return \"\";\n }\n\n const rangeData = this.rangeState[statePath];\n const currentItems = [...rangeData.items];\n const statics = rangeData.statics;\n\n for (const operation of operations) {\n if (!Array.isArray(operation) || operation.length < 2) {\n continue;\n }\n\n const opType = operation[0];\n\n switch (opType) {\n case \"r\": {\n const removeIndex = this.findItemIndexByKey(\n currentItems,\n operation[1],\n statics,\n statePath\n );\n if (removeIndex >= 0) {\n currentItems.splice(removeIndex, 1);\n }\n break;\n }\n case \"u\": {\n const updateIndex = this.findItemIndexByKey(\n currentItems,\n operation[1],\n statics,\n statePath\n );\n const changes = operation[2];\n if (updateIndex >= 0 && changes) {\n currentItems[updateIndex] = this.mergeRangeItem(\n currentItems[updateIndex],\n changes,\n statePath || \"\"\n );\n }\n break;\n }\n case \"a\": {\n this.addItemsToRange(\n currentItems,\n operation[1],\n operation[2],\n rangeData,\n false\n );\n if (\n operation[3] &&\n typeof operation[3] === \"object\" &&\n operation[3].idKey\n ) {\n this.rangeIdKeys[statePath || \"\"] = operation[3].idKey;\n }\n break;\n }\n case \"p\": {\n this.addItemsToRange(\n currentItems,\n operation[1],\n operation[2],\n rangeData,\n true\n );\n break;\n }\n case \"i\": {\n const targetIndex = this.findItemIndexByKey(\n currentItems,\n operation[1],\n statics,\n statePath\n );\n if (targetIndex >= 0) {\n const itemsToInsert = Array.isArray(operation[2])\n ? operation[2]\n : [operation[2]];\n currentItems.splice(targetIndex + 1, 0, ...itemsToInsert);\n }\n break;\n }\n case \"o\": {\n const newOrder = operation[1] as string[];\n const reorderedItems: any[] = [];\n const itemsByKey = new Map<string, any>();\n\n for (const item of currentItems) {\n const itemKey = this.getItemKey(item, statics, statePath);\n if (itemKey) {\n itemsByKey.set(itemKey, item);\n }\n }\n\n for (const orderedKey of newOrder) {\n const item = itemsByKey.get(orderedKey);\n if (item) {\n reorderedItems.push(item);\n }\n }\n\n currentItems.length = 0;\n currentItems.push(...reorderedItems);\n break;\n }\n default:\n break;\n }\n }\n\n this.rangeState[statePath] = {\n items: currentItems,\n statics: rangeData.statics,\n staticsMap: rangeData.staticsMap,\n };\n\n this.treeState[statePath] = {\n d: currentItems,\n s: rangeData.statics,\n sm: rangeData.staticsMap,\n };\n\n const rangeStructure = this.getCurrentRangeStructure(statePath);\n if (rangeStructure && rangeStructure.s) {\n return this.renderItemsWithStatics(\n currentItems,\n rangeStructure.s,\n rangeStructure.sm,\n statePath\n );\n }\n\n return currentItems.map((item) => this.renderValue(item)).join(\"\");\n }\n\n private getCurrentRangeStructure(stateKey: string): any {\n if (this.rangeState[stateKey]) {\n return {\n d: this.rangeState[stateKey].items,\n s: this.rangeState[stateKey].statics,\n sm: this.rangeState[stateKey].staticsMap,\n };\n }\n\n const fieldValue = this.treeState[stateKey];\n if (\n fieldValue &&\n typeof fieldValue === \"object\" &&\n (fieldValue as TreeNode).s\n ) {\n return fieldValue;\n }\n\n return null;\n }\n\n private renderItemsWithStatics(\n items: any[],\n statics: string[],\n staticsMap?: Record<string, string[]>,\n statePath?: string\n ): string {\n const result = items\n .map((item: any, itemIdx: number) => {\n // Get per-item statics from StaticsMap if available, otherwise use shared statics\n let itemStatics = statics;\n if (\n staticsMap &&\n typeof staticsMap === \"object\" &&\n item._sk &&\n staticsMap[item._sk]\n ) {\n itemStatics = staticsMap[item._sk];\n }\n\n let html = \"\";\n\n for (let i = 0; i < itemStatics.length; i++) {\n html += itemStatics[i];\n\n if (i < itemStatics.length - 1) {\n const fieldKey = i.toString();\n if (item[fieldKey] !== undefined) {\n const itemStatePath = statePath\n ? `${statePath}.${itemIdx}.${fieldKey}`\n : `${itemIdx}.${fieldKey}`;\n html += this.renderValue(item[fieldKey], fieldKey, itemStatePath);\n }\n }\n }\n\n return html;\n })\n .join(\"\");\n\n if (this.logger.isDebugEnabled()) {\n this.logger.debug(\"[renderItemsWithStatics] statics:\", statics);\n this.logger.debug(\"[renderItemsWithStatics] items count:\", items.length);\n this.logger.debug(\n \"[renderItemsWithStatics] result snippet:\",\n result.substring(0, 200)\n );\n }\n\n return result;\n }\n\n private addItemsToRange(\n currentItems: any[],\n items: any,\n statics: any[] | undefined,\n rangeData: RangeStateEntry,\n prepend: boolean\n ): void {\n if (statics) {\n rangeData.statics = statics;\n }\n\n if (!items) return;\n\n const itemsArray = Array.isArray(items) ? items : [items];\n if (prepend) {\n currentItems.unshift(...itemsArray);\n } else {\n currentItems.push(...itemsArray);\n }\n }\n\n private getItemKey(\n item: any,\n statics: any[],\n statePath?: string\n ): string | null {\n // First check for auto-generated _k field (always takes priority)\n if (item._k && typeof item._k === \"string\") {\n return item._k;\n }\n\n if (!statePath || !this.rangeIdKeys[statePath]) {\n return null;\n }\n\n const keyPosStr = this.rangeIdKeys[statePath];\n return item[keyPosStr] || null;\n }\n\n private findItemIndexByKey(\n items: any[],\n key: string,\n statics: any[],\n statePath?: string\n ): number {\n return items.findIndex(\n (item: any) => this.getItemKey(item, statics, statePath) === key\n );\n }\n\n /**\n * Merges changes into a range item using deep merge to preserve statics.\n * When the server sends partial updates like {\"5\": {\"0\": \"new text\"}},\n * we need to merge this into the existing item's field 5, not replace it.\n * Shallow spread would lose the statics ({\"s\": [...]}) that the client has cached.\n */\n private mergeRangeItem(item: any, changes: any, statePath: string): any {\n return this.deepMergeTreeNodes(item, changes, `${statePath}.item`);\n }\n}\n", "import type { ResponseMetadata } from \"../types\";\n\n/**\n * Tracks form submission lifecycle for LiveTemplate actions.\n */\nexport class FormLifecycleManager {\n private activeForm: HTMLFormElement | null = null;\n private activeButton: HTMLButtonElement | null = null;\n private originalButtonText: string | null = null;\n\n constructor() {}\n\n setActiveSubmission(\n form: HTMLFormElement | null,\n button: HTMLButtonElement | null,\n originalButtonText: string | null\n ): void {\n this.activeForm = form;\n this.activeButton = button;\n this.originalButtonText = originalButtonText;\n\n // Auto aria-busy + fieldset disabled for loading states\n if (form) {\n form.setAttribute(\"aria-busy\", \"true\");\n const fieldset = form.querySelector(\"fieldset\");\n if (fieldset) {\n fieldset.disabled = true;\n }\n }\n }\n\n handleResponse(meta: ResponseMetadata): void {\n if (this.activeForm) {\n this.activeForm.dispatchEvent(\n new CustomEvent(\"lvt:done\", { detail: meta })\n );\n }\n\n if (meta.success) {\n this.handleSuccess(meta);\n } else {\n this.handleError(meta);\n }\n\n this.restoreFormState();\n }\n\n reset(): void {\n this.restoreFormState();\n }\n\n private handleSuccess(meta: ResponseMetadata): void {\n if (!this.activeForm) {\n return;\n }\n\n this.activeForm.dispatchEvent(\n new CustomEvent(\"lvt:success\", { detail: meta })\n );\n\n // Close parent <dialog> using native API instead of ModalManager\n const dialogParent = this.activeForm.closest(\"dialog\");\n if (dialogParent && dialogParent.open) {\n dialogParent.close();\n }\n\n if (!this.activeForm.hasAttribute(\"lvt-form:preserve\")) {\n this.activeForm.reset();\n }\n }\n\n private handleError(meta: ResponseMetadata): void {\n if (!this.activeForm) {\n return;\n }\n\n this.activeForm.dispatchEvent(\n new CustomEvent(\"lvt:error\", { detail: meta })\n );\n }\n\n private restoreFormState(): void {\n if (this.activeForm) {\n this.activeForm.removeAttribute(\"aria-busy\");\n const fieldset = this.activeForm.querySelector(\"fieldset\");\n if (fieldset) {\n fieldset.disabled = false;\n }\n }\n\n if (this.activeButton && this.originalButtonText !== null) {\n this.activeButton.disabled = false;\n this.activeButton.textContent = this.originalButtonText;\n }\n\n this.activeForm = null;\n this.activeButton = null;\n this.originalButtonText = null;\n }\n}\n", "import { debounce } from \"../utils/rate-limit\";\nimport { DEFAULT_CHANGE_DEBOUNCE_MS } from \"../constants\";\nimport type { TreeNode } from \"../types\";\nimport type { Logger } from \"../utils/logger\";\n\nexport type BindingType = \"value\" | \"content\" | \"attribute\";\n\nexport interface ChangeMessage {\n action: \"change\";\n data: Record<string, string | boolean>;\n}\n\nexport interface ChangeAutoWirerContext {\n getWrapperElement(): Element | null;\n send(message: ChangeMessage): void;\n}\n\n/**\n * Analyzes template statics to detect form fields with dynamic values,\n * then auto-wires debounced input event listeners when the server\n * declares \"change\" capability.\n */\nexport class ChangeAutoWirer {\n private boundFields: Map<string, BindingType> = new Map();\n private enabled: boolean = false;\n private wiredElements: Set<Element> = new Set();\n private elementCleanups: Map<Element, () => void> = new Map();\n\n constructor(\n private readonly context: ChangeAutoWirerContext,\n private readonly logger: Logger\n ) {}\n\n setCapabilities(capabilities: string[]): void {\n this.enabled = capabilities.includes(\"change\");\n this.logger.debug(\n \"Capabilities received, change auto-wiring:\",\n this.enabled ? \"enabled\" : \"disabled\"\n );\n }\n\n analyzeStatics(treeState: TreeNode): void {\n const prevSize = this.boundFields.size;\n this.walkTree(treeState);\n if (this.boundFields.size > prevSize) {\n this.logger.debug(\n `Detected ${this.boundFields.size} bound field(s):`,\n Array.from(this.boundFields.keys())\n );\n }\n }\n\n wireElements(): void {\n if (!this.enabled) return;\n\n const wrapper = this.context.getWrapperElement();\n if (!wrapper) return;\n\n // Evict stale entries for elements removed by morphdom so they can be re-wired\n for (const el of this.elementCleanups.keys()) {\n if (!el.isConnected) {\n this.elementCleanups.get(el)!();\n this.elementCleanups.delete(el);\n this.wiredElements.delete(el);\n }\n }\n\n for (const [fieldName, bindingType] of this.boundFields) {\n const escapedName = this.escapeCSSSelector(fieldName);\n const elements = wrapper.querySelectorAll(`[name=\"${escapedName}\"]`);\n\n for (const el of elements) {\n if (this.wiredElements.has(el)) continue;\n if (el.hasAttribute(\"lvt-input\") || el.hasAttribute(\"lvt-change\"))\n continue;\n\n const parentForm = el.closest(\"form\");\n if (parentForm) {\n if (parentForm.hasAttribute(\"lvt-change\")) continue;\n if (parentForm.hasAttribute(\"lvt-form:no-intercept\")) continue;\n }\n\n if (el instanceof HTMLInputElement) {\n if (\n el.type === \"hidden\" ||\n el.type === \"submit\" ||\n el.type === \"button\"\n )\n continue;\n }\n if (el instanceof HTMLButtonElement) continue;\n\n this.attachListener(el as HTMLElement, fieldName, bindingType);\n this.wiredElements.add(el);\n }\n }\n\n // Auto-wire all named selects when Change() is enabled.\n // Select bindings live on child <option> tags ({{if eq ...}}selected{{end}}),\n // so they aren't detected by static analysis. A named select always submits a value.\n const selects = wrapper.querySelectorAll(\"select[name]\");\n for (const el of selects) {\n if (this.wiredElements.has(el)) continue;\n if (el.hasAttribute(\"lvt-input\") || el.hasAttribute(\"lvt-change\"))\n continue;\n\n const parentForm = el.closest(\"form\");\n if (parentForm) {\n if (parentForm.hasAttribute(\"lvt-change\")) continue;\n if (parentForm.hasAttribute(\"lvt-form:no-intercept\")) continue;\n }\n\n const name = el.getAttribute(\"name\");\n if (!name) continue;\n this.attachListener(el as HTMLElement, name, \"value\");\n this.wiredElements.add(el);\n }\n }\n\n teardown(): void {\n for (const cleanup of this.elementCleanups.values()) {\n cleanup();\n }\n this.elementCleanups.clear();\n this.wiredElements.clear();\n this.boundFields.clear();\n this.enabled = false;\n }\n\n getBoundFields(): ReadonlyMap<string, BindingType> {\n return this.boundFields;\n }\n\n isEnabled(): boolean {\n return this.enabled;\n }\n\n private walkTree(node: any): void {\n if (!node || typeof node !== \"object\" || Array.isArray(node)) return;\n\n if (node.s && Array.isArray(node.s)) {\n this.analyzeStaticsArray(node.s);\n }\n\n for (const key of Object.keys(node)) {\n if (/^\\d+$/.test(key)) {\n const child = node[key];\n if (child && typeof child === \"object\" && !Array.isArray(child)) {\n this.walkTree(child);\n }\n }\n }\n\n if (node.d && Array.isArray(node.d)) {\n for (const item of node.d) {\n if (item && typeof item === \"object\" && !Array.isArray(item)) {\n this.walkTree(item);\n }\n }\n }\n }\n\n private analyzeStaticsArray(statics: string[]): void {\n for (let i = 0; i < statics.length - 1; i++) {\n const left = statics[i];\n const right = statics[i + 1];\n\n const binding = this.detectBinding(left, right);\n if (binding && !this.boundFields.has(binding.fieldName)) {\n this.boundFields.set(binding.fieldName, binding.bindingType);\n }\n }\n }\n\n private detectBinding(\n left: string,\n right: string\n ): { fieldName: string; bindingType: BindingType } | null {\n let fieldName = this.detectValueBinding(left);\n if (fieldName) {\n return { fieldName, bindingType: \"value\" };\n }\n\n fieldName = this.detectTextareaBinding(left, right);\n if (fieldName) {\n return { fieldName, bindingType: \"content\" };\n }\n\n fieldName = this.detectAttributeBinding(left, right);\n if (fieldName) {\n return { fieldName, bindingType: \"attribute\" };\n }\n\n return null;\n }\n\n private escapeCSSSelector(value: string): string {\n if (typeof CSS !== \"undefined\" && typeof CSS.escape === \"function\") {\n return CSS.escape(value);\n }\n // Fallback for jsdom test environment only. Browsers always have CSS.escape.\n // This handles the common cases; field names with brackets/dots would need\n // CSS.escape which is not available in jsdom.\n return value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n }\n\n /**\n * Extract the unclosed tag fragment at the end of a static string.\n * Returns null if the string does not end inside an open tag.\n */\n private extractUnclosedTag(text: string): string | null {\n const lastOpen = text.lastIndexOf(\"<\");\n const lastClose = text.lastIndexOf(\">\");\n if (lastOpen === -1 || lastOpen <= lastClose) return null;\n return text.substring(lastOpen);\n }\n\n private extractNameFromTag(partialTag: string): string | null {\n const match = partialTag.match(/\\sname=\"([^\"]+)\"/);\n return match ? match[1] : null;\n }\n\n /** Detects: <input name=\"X\" ... value=\"{{.X}}\"> */\n private detectValueBinding(left: string): string | null {\n if (!left.endsWith('value=\"')) return null;\n const partialTag = this.extractUnclosedTag(left);\n if (!partialTag) return null;\n return this.extractNameFromTag(partialTag);\n }\n\n /** Detects: <textarea name=\"X\">{{.X}}</textarea> */\n private detectTextareaBinding(left: string, right: string): string | null {\n if (!right.startsWith(\"</textarea\")) return null;\n const tagMatch = left.match(/<textarea[^>]*\\sname=\"([^\"]+)\"[^>]*>$/);\n return tagMatch ? tagMatch[1] : null;\n }\n\n /** Detects: <input type=\"checkbox\" name=\"X\" {{if .X}}checked{{end}}> */\n private detectAttributeBinding(left: string, right: string): string | null {\n if (!left.endsWith(\" \")) return null;\n if (!/^[a-zA-Z>/\\s]/.test(right)) return null;\n\n const partialTag = this.extractUnclosedTag(left);\n if (!partialTag) return null;\n\n // Only checkbox/radio inputs use attribute bindings (checked),\n // since attachListener reads .checked for this binding type\n if (!/^<input\\s/i.test(partialTag)) return null;\n const typeMatch = partialTag.match(/\\stype=\"([^\"]+)\"/i);\n if (!typeMatch) return null;\n const inputType = typeMatch[1].toLowerCase();\n if (inputType !== \"checkbox\" && inputType !== \"radio\") return null;\n\n return this.extractNameFromTag(partialTag);\n }\n\n private attachListener(\n element: HTMLElement,\n fieldName: string,\n bindingType: BindingType\n ): void {\n const customDebounce = element.getAttribute(\"lvt-debounce\");\n const parsed = customDebounce ? parseInt(customDebounce, 10) : NaN;\n const wait =\n Number.isNaN(parsed) || parsed < 0 ? DEFAULT_CHANGE_DEBOUNCE_MS : parsed;\n\n const sendChange = () => {\n if (!this.enabled) return;\n\n const value: string | boolean =\n bindingType === \"attribute\" &&\n (element as HTMLInputElement).type === \"checkbox\"\n ? (element as HTMLInputElement).checked\n : (element as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement)\n .value;\n\n this.context.send({\n action: \"change\",\n data: { [fieldName]: value },\n });\n };\n\n const debouncedSend = debounce(sendChange, wait);\n\n // Discrete events (checkbox toggle, select change) use 'change';\n // continuous typing uses 'input'\n const eventType =\n bindingType === \"attribute\" || element instanceof HTMLSelectElement\n ? \"change\"\n : \"input\";\n\n element.addEventListener(eventType, debouncedSend);\n\n this.elementCleanups.set(element, () => {\n element.removeEventListener(eventType, debouncedSend);\n });\n\n this.logger.debug(\n `Auto-wired ${eventType} listener on [name=\"${fieldName}\"] (debounce: ${wait}ms)`\n );\n }\n}\n", "import type { LiveTemplateClientOptions, UpdateResponse } from \"../types\";\nimport type { Logger } from \"../utils/logger\";\n\nexport interface WebSocketTransportOptions {\n url: string;\n autoReconnect?: boolean;\n reconnectDelay?: number;\n maxReconnectDelay?: number; // Maximum delay between reconnect attempts (default: 16000ms)\n maxReconnectAttempts?: number; // Maximum number of reconnect attempts (default: 10, 0 = unlimited)\n onOpen?: (socket: WebSocket) => void;\n onMessage?: (event: MessageEvent<string>) => void;\n onClose?: (event: CloseEvent) => void;\n onReconnectAttempt?: (attempt: number, delay: number) => void;\n onReconnectFailed?: () => void; // Called when max reconnect attempts reached\n onError?: (event: Event) => void;\n}\n\n/**\n * Lightweight wrapper around browser WebSocket with optional auto-reconnect support.\n * Implements exponential backoff with jitter to prevent thundering herd.\n */\nexport class WebSocketTransport {\n private socket: WebSocket | null = null;\n private reconnectTimer: number | null = null;\n private manuallyClosed = false;\n private reconnectAttempts = 0;\n\n constructor(private readonly options: WebSocketTransportOptions) {}\n\n connect(): void {\n this.manuallyClosed = false;\n this.clearReconnectTimer();\n\n this.socket = new WebSocket(this.options.url);\n const socket = this.socket;\n\n socket.onopen = () => {\n // Reset reconnect attempts on successful connection\n this.reconnectAttempts = 0;\n this.options.onOpen?.(socket);\n };\n\n socket.onmessage = (event: MessageEvent<string>) => {\n this.options.onMessage?.(event);\n };\n\n socket.onclose = (event: CloseEvent) => {\n this.options.onClose?.(event);\n if (!this.manuallyClosed && this.options.autoReconnect) {\n this.scheduleReconnect();\n }\n };\n\n socket.onerror = (event: Event) => {\n this.options.onError?.(event);\n };\n }\n\n send(data: string): void {\n if (this.socket && this.socket.readyState === 1) { // WebSocket.OPEN = 1\n this.socket.send(data);\n }\n }\n\n disconnect(): void {\n this.manuallyClosed = true;\n this.clearReconnectTimer();\n if (this.socket) {\n this.socket.close();\n this.socket = null;\n }\n }\n\n getSocket(): WebSocket | null {\n return this.socket;\n }\n\n private scheduleReconnect(): void {\n this.clearReconnectTimer();\n\n // Check if max reconnect attempts reached\n const maxAttempts = this.options.maxReconnectAttempts ?? 10;\n if (maxAttempts > 0 && this.reconnectAttempts >= maxAttempts) {\n this.options.onReconnectFailed?.();\n return;\n }\n\n this.reconnectAttempts++;\n\n // Calculate exponential backoff: baseDelay * 2^attempt\n const baseDelay = this.options.reconnectDelay ?? 1000;\n const maxDelay = this.options.maxReconnectDelay ?? 16000;\n const exponentialDelay = baseDelay * Math.pow(2, this.reconnectAttempts - 1);\n\n // Add jitter: random value between 0 and 1000ms to prevent thundering herd\n const jitter = Math.random() * 1000;\n\n // Calculate final delay with maximum cap\n const delay = Math.min(exponentialDelay + jitter, maxDelay);\n\n this.reconnectTimer = window.setTimeout(() => {\n this.options.onReconnectAttempt?.(this.reconnectAttempts, delay);\n this.connect();\n }, delay);\n }\n\n private clearReconnectTimer(): void {\n if (this.reconnectTimer !== null) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n}\n\nexport interface WebSocketManagerConfig {\n options: LiveTemplateClientOptions;\n onConnected: () => void;\n onDisconnected: () => void;\n onMessage: (response: UpdateResponse, event: MessageEvent<string>) => void;\n onReconnectAttempt?: (attempt: number, delay: number) => void;\n onReconnectFailed?: () => void;\n onError?: (event: Event) => void;\n logger: Logger;\n}\n\nexport interface WebSocketConnectResult {\n usingWebSocket: boolean;\n initialState?: UpdateResponse | null;\n}\n\nexport class WebSocketManager {\n private transport: WebSocketTransport | null = null;\n // Optional override for liveUrl, set by the client on cross-handler\n // navigation. When set, takes precedence over options.liveUrl. This\n // avoids mutating the caller-provided options object.\n private liveUrlOverride: string | null = null;\n\n constructor(private readonly config: WebSocketManagerConfig) {}\n\n /**\n * Update the live URL used for WebSocket reconnection. Called by the\n * client on cross-handler navigation so the next connect() uses the\n * new page path without mutating the shared options object.\n */\n setLiveUrl(liveUrl: string): void {\n this.liveUrlOverride = liveUrl;\n }\n\n async connect(): Promise<WebSocketConnectResult> {\n const liveUrl = this.getLiveUrl();\n\n const wsAvailable = await checkWebSocketAvailability(\n liveUrl,\n this.config.logger\n );\n if (!wsAvailable) {\n const initialState = await fetchInitialState(liveUrl, this.config.logger);\n return { usingWebSocket: false, initialState };\n }\n\n // Await onopen before resolving, so downstream setup runs with a ready\n // transport. Without this, observer callbacks during CONNECTING fall\n // back to HTTP, which hits a separate server state path than the WS\n // event loop \u2014 producing stale/desynced responses.\n //\n // Three settle paths: onOpen (success), onClose/onError (immediate\n // failure), and a 10s timeout (silent network drop \u2014 TCP may never\n // surface a close/error event through a misbehaving middlebox).\n let resolveOpen!: (value: WebSocketConnectResult) => void;\n let rejectOpen!: (reason?: unknown) => void;\n const openPromise = new Promise<WebSocketConnectResult>((resolve, reject) => {\n resolveOpen = resolve;\n rejectOpen = reject;\n });\n let settled = false;\n // Tracks whether onOpen ever fired. Gates onDisconnected so we don't\n // fire a spurious \"disconnected\" notification on the HTTP fallback\n // path where the socket closed before it ever opened (either\n // naturally via onClose/onError, or because the catch block\n // explicitly disconnects the transport after the 10s timeout).\n let hasConnected = false;\n // Declared before settleOpen to avoid a temporal dead zone on the\n // closure reference \u2014 settleOpen is called from onOpen/onClose/onError\n // (all async, so safe today), but declaring the binding up front lets\n // any future synchronous call path still work without a ReferenceError.\n let openTimeoutId: ReturnType<typeof setTimeout> | null = null;\n const settleOpen = (err?: Error): void => {\n if (settled) return;\n settled = true;\n if (openTimeoutId !== null) {\n clearTimeout(openTimeoutId);\n openTimeoutId = null;\n }\n if (err) rejectOpen(err);\n else resolveOpen({ usingWebSocket: true });\n };\n openTimeoutId = setTimeout(() => {\n settleOpen(new Error(\"WebSocket open timed out after 10s\"));\n }, 10000);\n\n this.transport = new WebSocketTransport({\n url: this.getWebSocketUrl(),\n autoReconnect: this.config.options.autoReconnect,\n reconnectDelay: this.config.options.reconnectDelay,\n maxReconnectDelay: 16000, // 16 seconds maximum\n maxReconnectAttempts: 10, // 10 attempts before giving up\n onOpen: () => {\n hasConnected = true;\n this.config.onConnected();\n settleOpen();\n },\n onMessage: (event) => {\n try {\n const payload: UpdateResponse = JSON.parse(event.data);\n this.config.onMessage(payload, event);\n } catch (error) {\n this.config.logger.error(\"Failed to parse WebSocket message:\", error);\n }\n },\n onClose: () => {\n // Branch on whether we'd actually connected:\n // - Success path (onOpen fired): notify client of disconnection\n // via onDisconnected(). Do NOT call settleOpen \u2014 it's already\n // resolved, and we'd needlessly construct an Error object\n // that shows up in any log/trace capturing rejection reasons.\n // - Failure path (close before open): reject the openPromise\n // with a descriptive Error. onDisconnected is NOT fired\n // because we were never \"connected\" from the client's POV.\n if (hasConnected) {\n this.config.onDisconnected();\n } else {\n settleOpen(new Error(\"WebSocket closed before it opened\"));\n }\n },\n onReconnectAttempt: (attempt, delay) => {\n this.config.onReconnectAttempt?.(attempt, delay);\n },\n onReconnectFailed: () => {\n this.config.onReconnectFailed?.();\n },\n onError: (event) => {\n this.config.onError?.(event);\n // If we're already connected, onDisconnected is fired via the\n // subsequent onClose \u2014 per WHATWG WebSocket spec, onclose always\n // fires after onerror when the connection is lost post-open.\n // We rely on that invariant here rather than double-firing\n // onDisconnected (which would require tracking a separate\n // \"already disconnected\" flag).\n //\n // If we're NOT yet connected, this rejects openPromise so the\n // catch block falls back to HTTP. settleOpen is a no-op post-open.\n settleOpen(new Error(\"WebSocket errored before it opened\"));\n },\n });\n\n this.transport.connect();\n\n try {\n return await openPromise;\n } catch (err) {\n this.config.logger.warn(\"WebSocket open failed, falling back to HTTP\", err);\n // Stop the transport so a late onOpen or auto-reconnect doesn't fire\n // against a client that has already committed to HTTP mode.\n this.transport?.disconnect();\n this.transport = null;\n const initialState = await fetchInitialState(liveUrl, this.config.logger);\n return { usingWebSocket: false, initialState };\n }\n }\n\n disconnect(): void {\n this.transport?.disconnect();\n this.transport = null;\n }\n\n send(data: string): void {\n this.transport?.send(data);\n }\n\n getReadyState(): number | undefined {\n return this.transport?.getSocket()?.readyState;\n }\n\n getSocket(): WebSocket | null {\n return this.transport?.getSocket() ?? null;\n }\n\n private getWebSocketUrl(): string {\n const liveUrl = this.liveUrlOverride || this.config.options.liveUrl || \"/live\";\n const baseUrl = this.config.options.wsUrl;\n if (baseUrl) {\n return baseUrl;\n }\n const wsScheme = window.location.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n return `${wsScheme}//${window.location.host}${liveUrl}`;\n }\n\n private getLiveUrl(): string {\n return (\n this.liveUrlOverride ||\n this.config.options.liveUrl ||\n window.location.pathname + window.location.search\n );\n }\n}\n\nexport async function checkWebSocketAvailability(\n liveUrl: string,\n logger?: Logger\n): Promise<boolean> {\n try {\n const response = await fetch(liveUrl, {\n method: \"HEAD\",\n });\n\n const wsHeader = response.headers.get(\"X-LiveTemplate-WebSocket\");\n if (wsHeader) {\n return wsHeader === \"enabled\";\n }\n\n return true;\n } catch (error) {\n logger?.warn(\"Failed to check WebSocket availability:\", error);\n return true;\n }\n}\n\nexport async function fetchInitialState(\n liveUrl: string,\n logger?: Logger\n): Promise<UpdateResponse | null> {\n try {\n const response = await fetch(liveUrl, {\n method: \"GET\",\n credentials: \"include\",\n headers: {\n Accept: \"application/json\",\n },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to fetch initial state: ${response.status}`);\n }\n\n return (await response.json()) as UpdateResponse;\n } catch (error) {\n logger?.warn(\"Failed to fetch initial state:\", error);\n return null;\n }\n}\n", "/**\n * S3Uploader - Handles direct uploads to S3 using presigned URLs\n */\n\nimport type {\n ExternalUploadMeta,\n UploadEntry,\n UploadProgressCallback,\n Uploader,\n} from \"./types\";\n\nexport class S3Uploader implements Uploader {\n /**\n * Upload a file directly to S3 using presigned PUT URL\n */\n async upload(\n entry: UploadEntry,\n meta: ExternalUploadMeta,\n onProgress?: UploadProgressCallback\n ): Promise<void> {\n const { file } = entry;\n\n // Create abort controller for cancellation\n entry.abortController = new AbortController();\n\n try {\n // Create XMLHttpRequest for progress tracking\n const xhr = new XMLHttpRequest();\n\n // Track upload progress and notify handler\n xhr.upload.addEventListener(\"progress\", (e) => {\n if (e.lengthComputable) {\n entry.bytesUploaded = e.loaded;\n entry.progress = Math.round((e.loaded / e.total) * 100);\n // Notify progress callback\n if (onProgress) {\n onProgress(entry);\n }\n }\n });\n\n // Handle abort\n entry.abortController.signal.addEventListener(\"abort\", () => {\n xhr.abort();\n });\n\n // Create promise for upload completion\n const uploadPromise = new Promise<void>((resolve, reject) => {\n xhr.addEventListener(\"load\", () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n entry.done = true;\n entry.progress = 100;\n resolve();\n } else {\n reject(new Error(`S3 upload failed with status ${xhr.status}: ${xhr.statusText}`));\n }\n });\n\n xhr.addEventListener(\"error\", () => {\n reject(new Error(\"S3 upload failed: Network error\"));\n });\n\n xhr.addEventListener(\"abort\", () => {\n reject(new Error(\"S3 upload cancelled\"));\n });\n });\n\n // Open connection\n xhr.open(\"PUT\", meta.url);\n\n // Set headers\n if (meta.headers) {\n for (const [key, value] of Object.entries(meta.headers)) {\n xhr.setRequestHeader(key, value);\n }\n }\n\n // Send file\n xhr.send(file);\n\n await uploadPromise;\n } catch (error) {\n entry.error = error instanceof Error ? error.message : String(error);\n throw error;\n }\n }\n}\n", "/**\n * UploadHandler - Manages file uploads for LiveTemplate\n *\n * Handles:\n * - File input detection and change events\n * - Chunked uploads to server via WebSocket\n * - External uploads (S3) via presigned URLs\n * - Progress tracking and callbacks\n * - Upload cancellation\n */\n\nimport { S3Uploader } from \"./s3-uploader\";\nimport type {\n ExternalUploadMeta,\n FileMetadata,\n UploadChunkMessage,\n UploadCompleteMessage,\n UploadEntry,\n UploadHandlerOptions,\n UploadProgressMessage,\n UploadStartMessage,\n UploadStartResponse,\n Uploader,\n} from \"./types\";\n\nexport class UploadHandler {\n private entries: Map<string, UploadEntry> = new Map();\n private pendingFiles: Map<string, File[]> = new Map(); // uploadName -> files\n private autoUploadConfig: Map<string, boolean> = new Map(); // uploadName -> autoUpload\n private chunkSize: number;\n private uploaders: Map<string, Uploader> = new Map();\n private onProgress?: (entry: UploadEntry) => void;\n private onComplete?: (uploadName: string, entries: UploadEntry[]) => void;\n private onError?: (entry: UploadEntry, error: string) => void;\n private inputHandlers: WeakMap<HTMLInputElement, EventListener> = new WeakMap();\n\n constructor(\n private sendMessage: (message: any) => void,\n options: UploadHandlerOptions = {}\n ) {\n this.chunkSize = options.chunkSize || 256 * 1024; // 256KB default\n this.onProgress = options.onProgress;\n this.onComplete = options.onComplete;\n this.onError = options.onError;\n\n // Register default uploaders\n this.uploaders.set(\"s3\", new S3Uploader());\n }\n\n /**\n * Initialize upload detection on file inputs with lvt-upload attribute\n */\n initializeFileInputs(container: Element) {\n const inputs = container.querySelectorAll<HTMLInputElement>(\n 'input[type=\"file\"][lvt-upload]'\n );\n\n inputs.forEach((input) => {\n const uploadName = input.getAttribute(\"lvt-upload\");\n if (!uploadName) return;\n\n // Remove existing listener if any\n const existingHandler = this.inputHandlers.get(input);\n if (existingHandler) {\n input.removeEventListener(\"change\", existingHandler);\n }\n\n // Create new handler\n const handler = (e: Event) => {\n const files = (e.target as HTMLInputElement).files;\n if (!files || files.length === 0) return;\n\n // Always send upload_start to get validation and config\n // But only proceed with chunks if autoUpload is true\n this.startUpload(uploadName, Array.from(files));\n };\n\n input.addEventListener(\"change\", handler);\n // Store handler in WeakMap to prevent memory leaks\n this.inputHandlers.set(input, handler);\n });\n }\n\n /**\n * Start upload process for selected files\n */\n async startUpload(uploadName: string, files: File[]): Promise<void> {\n // Store files temporarily for when server response arrives\n this.pendingFiles.set(uploadName, files);\n\n // Create file metadata\n const fileMetadata: FileMetadata[] = files.map((file) => ({\n name: file.name,\n type: file.type || \"application/octet-stream\",\n size: file.size,\n }));\n\n // Send upload_start message to server\n const startMessage: UploadStartMessage = {\n action: \"upload_start\",\n upload_name: uploadName,\n files: fileMetadata,\n };\n\n this.sendMessage(startMessage);\n }\n\n /**\n * Handle upload_start response from server\n */\n async handleUploadStartResponse(\n response: UploadStartResponse\n ): Promise<void> {\n const { upload_name, entries: entryInfos } = response;\n\n // Store autoUpload configuration from first entry\n if (entryInfos.length > 0) {\n this.autoUploadConfig.set(upload_name, entryInfos[0].auto_upload);\n }\n\n // Get pending files for this upload\n const files = this.pendingFiles.get(upload_name);\n if (!files) {\n console.error(`No pending files found for upload: ${upload_name}`);\n return;\n }\n\n // Clear pending files\n this.pendingFiles.delete(upload_name);\n\n // Build a map from file name to file object for lookup\n const fileMap = new Map<string, File>();\n for (const file of files) {\n fileMap.set(file.name, file);\n }\n\n // Create upload entries\n const entries: UploadEntry[] = [];\n\n for (const info of entryInfos) {\n const file = fileMap.get(info.client_name);\n\n if (!file) {\n console.warn(\n `No file found for entry ${info.entry_id} (client_name: ${info.client_name})`\n );\n continue;\n }\n\n const entry: UploadEntry = {\n id: info.entry_id,\n file,\n uploadName: upload_name,\n progress: 0,\n bytesUploaded: 0,\n valid: info.valid,\n done: false,\n error: info.error,\n external: info.external,\n };\n\n this.entries.set(entry.id, entry);\n entries.push(entry);\n\n // Handle invalid entries\n if (!info.valid) {\n if (this.onError && info.error) {\n this.onError(entry, info.error);\n }\n continue;\n }\n\n // Only start upload immediately if autoUpload is true\n // Otherwise, entries are stored and will be uploaded on form submit\n if (info.auto_upload) {\n // Start upload (external or chunked)\n if (info.external) {\n this.uploadExternal(entry, info.external);\n } else {\n this.uploadChunked(entry);\n }\n }\n }\n }\n\n /**\n * Upload file using external uploader (S3, etc.)\n */\n private async uploadExternal(\n entry: UploadEntry,\n meta: ExternalUploadMeta\n ): Promise<void> {\n try {\n const uploader = this.uploaders.get(meta.uploader);\n if (!uploader) {\n throw new Error(`Unknown uploader: ${meta.uploader}`);\n }\n\n // Start external upload with progress callback\n await uploader.upload(entry, meta, this.onProgress);\n\n // Notify server of completion\n const completeMessage: UploadCompleteMessage = {\n action: \"upload_complete\",\n upload_name: entry.uploadName,\n entry_ids: [entry.id],\n };\n\n this.sendMessage(completeMessage);\n\n if (this.onComplete) {\n this.onComplete(entry.uploadName, [entry]);\n }\n\n // Clear the file input to prevent re-upload of the same file\n this.clearFileInput(entry.uploadName);\n\n // Schedule cleanup after completion\n this.cleanupEntries(entry.uploadName);\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n entry.error = errorMsg;\n if (this.onError) {\n this.onError(entry, errorMsg);\n }\n // Schedule cleanup after error\n this.cleanupEntries(entry.uploadName);\n }\n }\n\n /**\n * Upload file in chunks via WebSocket\n */\n private async uploadChunked(entry: UploadEntry): Promise<void> {\n const { file, id } = entry;\n let offset = 0;\n\n // Create abort controller for cancellation\n entry.abortController = new AbortController();\n\n try {\n while (offset < file.size) {\n // Check if upload was cancelled\n if (entry.abortController.signal.aborted) {\n throw new Error(\"Upload cancelled\");\n }\n // Read chunk\n const end = Math.min(offset + this.chunkSize, file.size);\n const chunk = file.slice(offset, end);\n\n // Convert to base64\n const base64 = await this.fileToBase64(chunk);\n\n // Send chunk\n const chunkMessage: UploadChunkMessage = {\n action: \"upload_chunk\",\n entry_id: id,\n chunk_base64: base64,\n offset,\n total: file.size,\n };\n\n this.sendMessage(chunkMessage);\n\n // Update progress\n offset = end;\n entry.bytesUploaded = offset;\n entry.progress = Math.round((offset / file.size) * 100);\n\n if (this.onProgress) {\n this.onProgress(entry);\n }\n }\n\n // Send complete message\n entry.done = true;\n const completeMessage: UploadCompleteMessage = {\n action: \"upload_complete\",\n upload_name: entry.uploadName,\n entry_ids: [id],\n };\n\n this.sendMessage(completeMessage);\n\n if (this.onComplete) {\n this.onComplete(entry.uploadName, [entry]);\n }\n\n // Clear the file input to prevent re-upload of the same file\n this.clearFileInput(entry.uploadName);\n\n // Schedule cleanup after completion\n this.cleanupEntries(entry.uploadName);\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n entry.error = errorMsg;\n if (this.onError) {\n this.onError(entry, errorMsg);\n }\n // Schedule cleanup after error\n this.cleanupEntries(entry.uploadName);\n }\n }\n\n /**\n * Handle progress message from server (for chunked uploads)\n */\n handleProgressMessage(message: UploadProgressMessage): void {\n const entry = this.entries.get(message.entry_id);\n if (!entry) return;\n\n entry.progress = message.progress;\n entry.bytesUploaded = message.bytes_recv;\n\n if (this.onProgress) {\n this.onProgress(entry);\n }\n }\n\n /**\n * Cancel upload\n */\n cancelUpload(entryId: string): void {\n const entry = this.entries.get(entryId);\n if (!entry) return;\n\n // Abort external upload if in progress\n if (entry.abortController) {\n entry.abortController.abort();\n }\n\n // Send cancel message to server\n this.sendMessage({\n action: \"cancel_upload\",\n entry_id: entryId,\n });\n\n // Remove entry\n this.entries.delete(entryId);\n }\n\n /**\n * Get all entries for an upload name\n */\n getEntries(uploadName: string): UploadEntry[] {\n const entries: UploadEntry[] = [];\n for (const entry of this.entries.values()) {\n if (entry.uploadName === uploadName) {\n entries.push(entry);\n }\n }\n return entries;\n }\n\n /**\n * Trigger upload for all pending entries (used when autoUpload is false)\n * Called by LiveTemplate client on form submit\n */\n triggerPendingUploads(uploadName: string): void {\n // Get all entries for this upload that haven't started yet\n const pendingEntries: UploadEntry[] = [];\n for (const entry of this.entries.values()) {\n if (\n entry.uploadName === uploadName &&\n entry.progress === 0 &&\n !entry.done &&\n !entry.error\n ) {\n pendingEntries.push(entry);\n }\n }\n\n // Start uploads\n for (const entry of pendingEntries) {\n if (entry.external) {\n this.uploadExternal(entry, entry.external);\n } else {\n this.uploadChunked(entry);\n }\n }\n }\n\n /**\n * Register custom uploader\n */\n registerUploader(name: string, uploader: Uploader): void {\n this.uploaders.set(name, uploader);\n }\n\n /**\n * Clear file input to prevent re-upload of the same file\n * Called after successful upload completion\n */\n private clearFileInput(uploadName: string): void {\n // Find all file inputs with this upload name\n const inputs = document.querySelectorAll<HTMLInputElement>(\n `input[type=\"file\"][lvt-upload=\"${uploadName}\"]`\n );\n\n inputs.forEach((input) => {\n // Clear the file input value\n input.value = '';\n });\n }\n\n /**\n * Clean up completed or errored upload entries to prevent memory leaks\n * Automatically called after completion/error, but can be called manually\n * @param uploadName - Optional upload name to clean specific uploads\n * @param delay - Optional delay in ms before cleanup (default: 5000ms)\n */\n cleanupEntries(uploadName?: string, delay: number = 5000): void {\n setTimeout(() => {\n const entriesToRemove: string[] = [];\n\n for (const [id, entry] of this.entries) {\n // Skip if uploadName is specified and doesn't match\n if (uploadName && entry.uploadName !== uploadName) continue;\n\n // Remove completed or errored entries\n if (entry.done || entry.error) {\n entriesToRemove.push(id);\n }\n }\n\n for (const id of entriesToRemove) {\n this.entries.delete(id);\n }\n }, delay);\n }\n\n /**\n * Convert File/Blob to base64 string\n */\n private fileToBase64(blob: Blob): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => {\n const result = reader.result as string;\n // Remove data URL prefix\n const base64 = result.split(\",\")[1];\n resolve(base64);\n };\n reader.onerror = reject;\n reader.readAsDataURL(blob);\n });\n }\n}\n", "export type LogLevel = \"silent\" | \"error\" | \"warn\" | \"info\" | \"debug\";\n\nconst levelPriority: Record<LogLevel, number> = {\n silent: 0,\n error: 1,\n warn: 2,\n info: 3,\n debug: 4,\n};\n\ninterface LogState {\n level: LogLevel;\n}\n\ntype ConsoleMethod = \"error\" | \"warn\" | \"info\" | \"debug\";\n\nconst DEFAULT_SCOPE = \"LiveTemplate\";\n\n/**\n * Lightweight console logger with support for log levels and scoped prefixes.\n */\nexport class Logger {\n constructor(\n private readonly state: LogState,\n private readonly scope: string[] = [],\n private readonly sink: Console = console\n ) {}\n\n setLevel(level: LogLevel): void {\n this.state.level = level;\n }\n\n getLevel(): LogLevel {\n return this.state.level;\n }\n\n child(scope: string): Logger {\n return new Logger(this.state, [...this.scope, scope], this.sink);\n }\n\n isDebugEnabled(): boolean {\n return this.shouldLog(\"debug\");\n }\n\n error(...args: unknown[]): void {\n this.log(\"error\", \"error\", args);\n }\n\n warn(...args: unknown[]): void {\n this.log(\"warn\", \"warn\", args);\n }\n\n info(...args: unknown[]): void {\n this.log(\"info\", \"info\", args);\n }\n\n debug(...args: unknown[]): void {\n this.log(\"debug\", \"debug\", args);\n }\n\n private log(level: LogLevel, method: ConsoleMethod, args: unknown[]): void {\n if (!this.shouldLog(level)) {\n return;\n }\n\n const target =\n (this.sink[method] as (...args: unknown[]) => void) ||\n (console[method] as (...args: unknown[]) => void) ||\n console.log;\n target.apply(this.sink, [this.formatPrefix(), ...args]);\n }\n\n private shouldLog(level: LogLevel): boolean {\n return levelPriority[level] <= levelPriority[this.state.level];\n }\n\n private formatPrefix(): string {\n if (this.scope.length === 0) {\n return `[${DEFAULT_SCOPE}]`;\n }\n\n return `[${DEFAULT_SCOPE}:${this.scope.join(\":\")}]`;\n }\n}\n\nexport interface LoggerOptions {\n level?: LogLevel;\n scope?: string | string[];\n sink?: Console;\n}\n\nexport function createLogger(options: LoggerOptions = {}): Logger {\n const state: LogState = {\n level: options.level ?? \"info\",\n };\n\n const scope = Array.isArray(options.scope)\n ? options.scope\n : options.scope\n ? [options.scope]\n : [];\n\n return new Logger(state, scope, options.sink ?? console);\n}\n", "import type { LiveTemplateClient } from \"../livetemplate-client\";\nimport type { UpdateResult } from \"../types\";\n\n/**\n * Utility function to load and apply updates from JSON files.\n */\nexport async function loadAndApplyUpdate(\n client: LiveTemplateClient,\n updatePath: string\n): Promise<UpdateResult> {\n try {\n const nodeRequire = (globalThis as any)?.require;\n if (typeof nodeRequire === \"function\") {\n const fs = nodeRequire(\"fs\");\n const updateData = JSON.parse(fs.readFileSync(updatePath, \"utf8\"));\n return client.applyUpdate(updateData);\n }\n\n const response = await fetch(updatePath);\n const updateData = await response.json();\n return client.applyUpdate(updateData);\n } catch (error) {\n throw new Error(`Failed to load update from ${updatePath}: ${error}`);\n }\n}\n\n/**\n * Compare two HTML strings, ignoring whitespace differences.\n */\nexport function compareHTML(\n expected: string,\n actual: string\n): {\n match: boolean;\n differences: string[];\n} {\n const differences: string[] = [];\n\n const normalizeHTML = (html: string) => {\n return html.replace(/\\s+/g, \" \").replace(/>\\s+</g, \"><\").trim();\n };\n\n const normalizedExpected = normalizeHTML(expected);\n const normalizedActual = normalizeHTML(actual);\n\n if (normalizedExpected === normalizedActual) {\n return { match: true, differences: [] };\n }\n\n const expectedLines = normalizedExpected.split(\"\\n\");\n const actualLines = normalizedActual.split(\"\\n\");\n const maxLines = Math.max(expectedLines.length, actualLines.length);\n\n for (let i = 0; i < maxLines; i++) {\n const expectedLine = expectedLines[i] || \"\";\n const actualLine = actualLines[i] || \"\";\n\n if (expectedLine !== actualLine) {\n differences.push(`Line ${i + 1}:`);\n differences.push(` Expected: ${expectedLine}`);\n differences.push(` Actual: ${actualLine}`);\n }\n }\n\n return { match: false, differences };\n}\n"],
5
- "mappings": "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",
6
- "names": ["livetemplate_client_exports", "__export", "LiveTemplateClient", "compareHTML", "loadAndApplyUpdate", "setupReactiveAttributeListeners", "DOCUMENT_FRAGMENT_NODE", "morphAttrs", "fromNode", "toNode", "toNodeAttrs", "attr", "attrName", "attrNamespaceURI", "attrValue", "fromValue", "i", "fromNodeAttrs", "range", "NS_XHTML", "doc", "HAS_TEMPLATE_SUPPORT", "HAS_RANGE_SUPPORT", "createFragmentFromTemplate", "str", "template", "createFragmentFromRange", "fragment", "createFragmentFromWrap", "toElement", "compareNodeNames", "fromEl", "toEl", "fromNodeName", "toNodeName", "fromCodeStart", "toCodeStart", "createElementNS", "name", "namespaceURI", "moveChildren", "curChild", "nextChild", "syncBooleanAttrProp", "specialElHandlers", "parentNode", "parentName", "newValue", "firstChild", "oldValue", "selectedIndex", "optgroup", "nodeName", "ELEMENT_NODE", "DOCUMENT_FRAGMENT_NODE$1", "TEXT_NODE", "COMMENT_NODE", "noop", "defaultGetNodeKey", "node", "morphdomFactory", "options", "toNodeHtml", "getNodeKey", "onBeforeNodeAdded", "onNodeAdded", "onBeforeElUpdated", "onElUpdated", "onBeforeNodeDiscarded", "onNodeDiscarded", "onBeforeElChildrenUpdated", "skipFromChildren", "addChild", "parent", "child", "childrenOnly", "fromNodesLookup", "keyedRemovalList", "addKeyedRemoval", "key", "walkDiscardedChildNodes", "skipKeyedNodes", "removeNode", "indexTree", "handleNodeAdded", "el", "nextSibling", "unmatchedFromEl", "morphEl", "cleanupFromEl", "curFromNodeChild", "curFromNodeKey", "fromNextSibling", "toElKey", "beforeUpdateResult", "morphChildren", "skipFrom", "curToNodeChild", "curToNodeKey", "toNextSibling", "matchingFromEl", "outer", "curFromNodeType", "isCompatible", "onBeforeNodeAddedResult", "specialElHandler", "morphedNode", "morphedNodeType", "toNodeType", "len", "elToRemove", "morphdom", "morphdom_esm_default", "FOCUSABLE_INPUTS", "FocusManager", "logger", "wrapper", "selector", "FOCUSABLE_INPUTS", "type", "wrapperId", "focusKey", "blurKey", "focusListener", "event", "_a", "target", "blurListener", "_b", "_c", "element", "index", "wasFocused", "selectionStart", "selectionEnd", "el", "LIFECYCLE_EVENTS", "LIFECYCLE_SET", "SYNTHETIC_TRIGGERS", "METHOD_MAP", "parseReactiveAttribute", "attrName", "attrValue", "newMatch", "methodKey", "action", "eventPart", "segments", "lastSegment", "lifecycle", "actionName", "resolveTarget", "element", "selector", "executeAction", "param", "classes", "c", "colonIndex", "name", "value", "matchesEvent", "binding", "processReactiveAttributes", "methodKeys", "selectorParts", "escapedAction", "m", "candidates", "e", "fallbackParts", "attr", "processElementInteraction", "trigger", "match", "isDOMEventTrigger", "setupReactiveAttributeListeners", "_a", "FX_LIFECYCLE_SET", "animatedElements", "parseFxTrigger", "attrName", "onMatch", "parts", "setupFxDOMEventTriggers", "scanRoot", "registryRoot", "registry", "fxListenersKey", "fxListeners", "entry", "processEl", "el", "_a", "attr", "parsed", "FX_LIFECYCLE_SET", "SYNTHETIC_TRIGGERS", "listenerKey", "effect", "attrNameCapture", "listener", "currentValue", "applyFxEffect", "teardownFxDOMEventTriggers", "rootElement", "listeners", "event", "handler", "guardKey", "processFxLifecycleAttributes", "lifecycle", "actionName", "htmlElement", "config", "computed", "duration", "color", "originalBackground", "originalTransition", "animatedElements", "animation", "animationValue", "rawBehavior", "behavior", "VALID_SCROLL_BEHAVIORS", "threshold", "mode", "setupFxLifecycleListeners", "e", "teardownFxLifecycleListeners", "handleScrollDirectives", "element", "handleHighlightDirectives", "handleAnimateDirectives", "ensureAnimateKeyframes", "style", "PENDING_PROCESSED_KEY", "handleToastDirectives", "trigger", "pending", "messages", "position", "stack", "getOrCreateToastStack", "msg", "createToastElement", "setupToastClickOutside", "key", "existing", "applyPositionStyles", "s", "inner", "t", "b", "btn", "debounce", "func", "wait", "timeout", "args", "context", "throttle", "limit", "inThrottle", "lvtSelector", "attr", "value", "escaped", "safeValue", "CLICK_AWAY_METHOD_MAP", "CLICK_AWAY_METHODS", "NON_BUBBLING", "DELEGATED_EVENT_TYPES", "EventDelegator", "context", "logger", "wrapperId", "eventType", "listenerKey", "existingListener", "button", "data", "attr", "key", "wrapperElement", "eventTypes", "rateLimitedHandlers", "listener", "e", "_a", "currentWrapper", "target", "element", "inWrapper", "attrName", "action", "actionElement", "isOrphanButton", "persistTable", "btn", "explicitAction", "submitter", "dialog", "keyFilter", "targetElement", "handleAction", "message", "formData", "checkboxes", "checkboxNames", "cb", "name", "passwordFields", "el", "submitterForData", "actionFieldName", "value", "submitter2", "tier1FormData", "tier1FileInputs", "input", "submitButton", "originalButtonText", "uploadName", "dialogToClose", "throttleValue", "debounceValue", "handlerCache", "cacheKey", "callbackRefKey", "elementWithCallback", "rateLimitedHandler", "callLatest", "limit", "throttle", "wait", "debounce", "windowEvents", "lvtSelector", "clickAwaySelector", "m", "match", "method", "executeAction", "resolveTarget", "scanRoot", "delegatedKey", "delegated", "listenersKey", "allListeners", "entry", "root", "processEl", "triggers", "trigger", "isDOMEventTrigger", "processElementInteraction", "escaped", "triggerPattern", "handler", "hasMatch", "a", "listeners", "event", "guardKey", "getFocusableElements", "container", "selector", "htmlEl", "style", "isNotDisplayNone", "isNotVisibilityHidden", "hasLayoutContext", "focusTrapElements", "activeTrap", "trap", "htmlTrap", "focusableElements", "firstElement", "lastElement", "observerKey", "existingObserver", "processAutofocus", "htmlElement", "isVisible", "wasFocused", "observer", "mutations", "shouldProcess", "mutation", "node", "LinkInterceptor", "context", "logger", "wrapperId", "_a", "listenerKey", "existing", "wrapper", "listener", "e", "target", "currentWrapper", "prevHref", "link", "protocol", "href", "pushState", "_b", "_c", "targetURL", "refURL", "response", "html", "_ObserverManager", "context", "logger", "wrapperElement", "sentinel", "entries", "wrapper", "e", "detail", "ObserverManager", "LoadingIndicator", "bar", "style", "FormDisabler", "wrapper", "form", "input", "installed", "handleClick", "e", "el", "button", "targetId", "command", "target", "setupInvokerPolyfill", "deepClone", "obj", "isRangeNode", "node", "hasRangeAnywhere", "depth", "key", "child", "shouldFullReplace", "existing", "update", "TreeRenderer", "logger", "changed", "value", "oldValue", "newValue", "currentPath", "_a", "merged", "fieldPath", "isDifferentialOps", "existingIsRange", "rangeStructure", "operations", "statePath", "currentItems", "operation", "removeIndex", "updateIndex", "changes", "itemsToAdd", "itemsToPrepend", "targetIndex", "itemsToInsert", "newOrder", "reorderedItems", "itemsByKey", "item", "itemKey", "orderedKey", "html", "i", "staticSegment", "dynamicKey", "newStatePath", "fieldKey", "stateKey", "keys", "numericKeys", "k", "b", "itemStatePath", "idx", "rangeNode", "dynamics", "statics", "staticsMap", "elseKey", "elseStatePath", "hasStaticsMap", "itemIdx", "itemStatics", "localKey", "rangeData", "fieldValue", "items", "result", "prepend", "itemsArray", "keyPosStr", "FormLifecycleManager", "form", "button", "originalButtonText", "fieldset", "meta", "dialogParent", "ChangeAutoWirer", "context", "logger", "capabilities", "treeState", "prevSize", "wrapper", "el", "fieldName", "bindingType", "escapedName", "elements", "parentForm", "selects", "name", "cleanup", "node", "key", "child", "item", "statics", "i", "left", "right", "binding", "value", "text", "lastOpen", "lastClose", "partialTag", "match", "tagMatch", "typeMatch", "inputType", "element", "customDebounce", "parsed", "wait", "debouncedSend", "debounce", "eventType", "WebSocketTransport", "options", "socket", "_a", "_b", "event", "data", "_c", "_d", "_e", "maxAttempts", "baseDelay", "maxDelay", "exponentialDelay", "jitter", "delay", "WebSocketManager", "config", "liveUrl", "checkWebSocketAvailability", "fetchInitialState", "resolveOpen", "rejectOpen", "openPromise", "resolve", "reject", "settled", "hasConnected", "openTimeoutId", "settleOpen", "err", "payload", "error", "attempt", "baseUrl", "logger", "wsHeader", "response", "S3Uploader", "entry", "meta", "onProgress", "file", "xhr", "e", "uploadPromise", "resolve", "reject", "key", "value", "error", "UploadHandler", "sendMessage", "options", "S3Uploader", "container", "input", "uploadName", "existingHandler", "handler", "e", "files", "fileMetadata", "file", "startMessage", "response", "upload_name", "entryInfos", "fileMap", "entries", "info", "entry", "meta", "uploader", "completeMessage", "error", "errorMsg", "id", "offset", "end", "chunk", "base64", "chunkMessage", "message", "entryId", "pendingEntries", "name", "delay", "entriesToRemove", "blob", "resolve", "reject", "reader", "levelPriority", "DEFAULT_SCOPE", "Logger", "_Logger", "state", "scope", "sink", "level", "args", "method", "createLogger", "options", "_a", "_b", "loadAndApplyUpdate", "client", "updatePath", "nodeRequire", "fs", "updateData", "error", "compareHTML", "expected", "actual", "differences", "normalizeHTML", "html", "normalizedExpected", "normalizedActual", "expectedLines", "actualLines", "maxLines", "i", "expectedLine", "actualLine", "LiveTemplateClient", "_LiveTemplateClient", "options", "providedLogger", "logLevel", "debug", "restOptions", "resolvedLevel", "baseLogger", "createLogger", "TreeRenderer", "FocusManager", "FormLifecycleManager", "LoadingIndicator", "FormDisabler", "UploadHandler", "message", "entry", "uploadName", "entries", "error", "EventDelegator", "value", "form", "action", "formData", "button", "originalButtonText", "LinkInterceptor", "html", "href", "ObserverManager", "ChangeAutoWirer", "WebSocketManager", "_a", "_b", "_c", "response", "event", "autoInitLogger", "init", "wrapper", "client", "uploadMessage", "startResponse", "wrapperSelector", "connectionResult", "setupToastClickOutside", "setupReactiveAttributeListeners", "setupInvokerPolyfill", "setupFxLifecycleListeners", "teardownFxDOMEventTriggers", "teardownFxLifecycleListeners", "url", "flashParams", "hasFlashParams", "param", "readyState", "data", "seenKeys", "v", "k", "newLiveUrl", "liveUrl", "updateResponse", "doc", "oldId", "newTitleText", "newWrapper", "newId", "myEpoch", "n", "selector", "err", "body", "trimmed", "num", "update", "existingHTML", "result", "match", "innerContent", "newBodyContent", "element", "meta", "hasStaticsInTree", "node", "tempWrapper", "rawTag", "wrapTag", "root", "morphdom_esm_default", "fromEl", "toEl", "fromAttrs", "toElement", "i", "attr", "el", "handleScrollDirectives", "handleHighlightDirectives", "handleAnimateDirectives", "setupFxDOMEventTriggers", "handleToastDirectives", "hookName", "hookValue"]
4
+ "sourcesContent": ["/**\n * LiveTemplate TypeScript Client\n *\n * Reconstructs HTML from tree-based updates using cached static structure,\n * following the Phoenix LiveView optimization approach.\n */\n\nimport morphdom from \"morphdom\";\nimport { FocusManager } from \"./dom/focus-manager\";\nimport {\n handleAnimateDirectives,\n handleHighlightDirectives,\n handleScrollDirectives,\n handleToastDirectives,\n setupToastClickOutside,\n setupFxDOMEventTriggers,\n teardownFxDOMEventTriggers,\n setupFxLifecycleListeners,\n teardownFxLifecycleListeners,\n} from \"./dom/directives\";\nimport { EventDelegator } from \"./dom/event-delegation\";\nimport { LinkInterceptor } from \"./dom/link-interceptor\";\nimport { ObserverManager } from \"./dom/observer-manager\";\nimport { LoadingIndicator } from \"./dom/loading-indicator\";\nimport { FormDisabler } from \"./dom/form-disabler\";\nimport { setupReactiveAttributeListeners } from \"./dom/reactive-attributes\";\nimport { setupInvokerPolyfill } from \"./dom/invoker-polyfill\";\nimport { TreeRenderer } from \"./state/tree-renderer\";\nimport { FormLifecycleManager } from \"./state/form-lifecycle-manager\";\nimport { ChangeAutoWirer } from \"./state/change-auto-wirer\";\nimport { WebSocketManager } from \"./transport/websocket\";\nimport { UploadHandler } from \"./upload/upload-handler\";\nimport type {\n UploadProgressMessage,\n UploadStartResponse,\n} from \"./upload/types\";\nimport type {\n LiveTemplateClientOptions,\n ResponseMetadata,\n TreeNode,\n UpdateResponse,\n UpdateResult,\n} from \"./types\";\nimport { createLogger, Logger } from \"./utils/logger\";\nexport { loadAndApplyUpdate, compareHTML } from \"./utils/testing\";\nexport { setupReactiveAttributeListeners } from \"./dom/reactive-attributes\";\n\nexport class LiveTemplateClient {\n private readonly treeRenderer: TreeRenderer;\n private readonly focusManager: FocusManager;\n private readonly logger: Logger;\n private lvtId: string | null = null;\n\n // Transport properties\n private webSocketManager: WebSocketManager;\n public ws: WebSocket | null = null;\n private wrapperElement: Element | null = null;\n private options: LiveTemplateClientOptions;\n private useHTTP: boolean = false; // True when WebSocket is unavailable\n private sessionCookie: string | null = null; // For HTTP mode session tracking\n\n // Rate limiting: cache of debounced/throttled handlers per element+eventType\n private rateLimitedHandlers: WeakMap<Element, Map<string, Function>> =\n new WeakMap();\n\n private eventDelegator: EventDelegator;\n private linkInterceptor: LinkInterceptor;\n private observerManager: ObserverManager;\n private formLifecycleManager: FormLifecycleManager;\n private loadingIndicator: LoadingIndicator;\n private formDisabler: FormDisabler;\n private uploadHandler: UploadHandler;\n private changeAutoWirer: ChangeAutoWirer;\n\n // Initialization tracking for loading indicator\n private isInitialized: boolean = false;\n\n // Message tracking for deterministic E2E testing\n private messageCount: number = 0;\n\n // Cross-handler navigation: track the latest in-flight connect() so a\n // subsequent navigation can supersede an earlier one. Incremented on\n // each cross-handler navigation; handlers check the epoch to avoid\n // applying stale connection results.\n private navigationEpoch: number = 0;\n\n // Override for the live URL used by HTTP send and multipart methods.\n // Updated on cross-handler navigation so HTTP requests go to the new\n // handler path. When null, falls back to options.liveUrl. We avoid\n // mutating the options object so callers holding a reference don't\n // observe side-effects.\n private liveUrlOverride: string | null = null;\n\n constructor(options: LiveTemplateClientOptions = {}) {\n const { logger: providedLogger, logLevel, debug, ...restOptions } = options;\n const resolvedLevel = logLevel ?? (debug ? \"debug\" : \"info\");\n const baseLogger = providedLogger ?? createLogger({ level: resolvedLevel });\n\n if (providedLogger) {\n if (logLevel) {\n providedLogger.setLevel(logLevel);\n } else if (debug) {\n providedLogger.setLevel(\"debug\");\n }\n } else {\n baseLogger.setLevel(resolvedLevel);\n }\n\n this.logger = baseLogger.child(\"Client\");\n\n this.options = {\n autoReconnect: false, // Disable autoReconnect by default to avoid connection loops\n reconnectDelay: 1000,\n // liveUrl captures the current page URL (path + search) so the\n // initial WebSocket handshake reaches the server with the same\n // query params Mount saw on the HTTP GET. This is intentional \u2014\n // without the search, the WS-side Mount re-runs with empty data\n // and drifts state from the HTTP render.\n //\n // For *same-handler* SPA navigation (changing query params on\n // the same path), the client does NOT reconnect \u2014 instead it\n // sends an in-band {action:\"__navigate__\", data:<params>} message\n // over the existing WebSocket, and the server re-runs Mount with\n // the new data. See sendNavigate() and handleNavigationResponse()\n // for the SPA path. Cross-handler SPA navigation still does the\n // fetch-and-replaceChildren+reconnect dance.\n liveUrl: window.location.pathname + window.location.search,\n ...restOptions,\n };\n\n this.treeRenderer = new TreeRenderer(this.logger.child(\"TreeRenderer\"));\n this.focusManager = new FocusManager(this.logger.child(\"FocusManager\"));\n\n this.formLifecycleManager = new FormLifecycleManager();\n this.loadingIndicator = new LoadingIndicator();\n this.formDisabler = new FormDisabler();\n\n // Initialize upload handler\n this.uploadHandler = new UploadHandler(\n (message) => this.send(message),\n {\n chunkSize: 256 * 1024, // 256KB chunks\n onProgress: (entry) => {\n // Trigger DOM update to refresh upload progress\n if (this.wrapperElement) {\n this.wrapperElement.dispatchEvent(\n new CustomEvent(\"lvt:upload:progress\", {\n detail: { entry },\n })\n );\n }\n },\n onComplete: (uploadName, entries) => {\n this.logger.info(`Upload complete: ${uploadName}`, entries);\n if (this.wrapperElement) {\n this.wrapperElement.dispatchEvent(\n new CustomEvent(\"lvt:upload:complete\", {\n detail: { uploadName, entries },\n })\n );\n }\n },\n onError: (entry, error) => {\n this.logger.error(`Upload error for ${entry.id}:`, error);\n if (this.wrapperElement) {\n this.wrapperElement.dispatchEvent(\n new CustomEvent(\"lvt:upload:error\", {\n detail: { entry, error },\n })\n );\n }\n },\n }\n );\n\n this.eventDelegator = new EventDelegator(\n {\n getWrapperElement: () => this.wrapperElement,\n getRateLimitedHandlers: () => this.rateLimitedHandlers,\n parseValue: (value: string) => this.parseValue(value),\n send: (message: any) => this.send(message),\n sendHTTPMultipart: (form: HTMLFormElement, action: string, formData: FormData) =>\n this.sendHTTPMultipart(form, action, formData),\n setActiveSubmission: (\n form: HTMLFormElement | null,\n button: HTMLButtonElement | null,\n originalButtonText: string | null\n ) =>\n this.formLifecycleManager.setActiveSubmission(\n form,\n button,\n originalButtonText\n ),\n getWebSocketReadyState: () => this.webSocketManager.getReadyState(),\n triggerPendingUploads: (uploadName: string) =>\n this.uploadHandler.triggerPendingUploads(uploadName),\n },\n this.logger.child(\"EventDelegator\")\n );\n\n this.linkInterceptor = new LinkInterceptor(\n {\n getWrapperElement: () => this.wrapperElement,\n handleNavigationResponse: (html: string) => this.handleNavigationResponse(html),\n sendNavigate: (href: string) => this.sendNavigate(href),\n // Only take the in-band fast path when the WS is actually OPEN.\n // If WS is CONNECTING or CLOSED, falling through to the normal fetch\n // path is safer: pushState fires after the fetch resolves (not before),\n // so the browser URL never gets ahead of server state.\n canSendNavigate: () =>\n !this.useHTTP &&\n this.webSocketManager.getReadyState() === 1 /* WebSocket.OPEN */,\n },\n this.logger.child(\"LinkInterceptor\")\n );\n\n this.observerManager = new ObserverManager(\n {\n getWrapperElement: () => this.wrapperElement,\n send: (message: any) => this.send(message),\n },\n this.logger.child(\"ObserverManager\")\n );\n\n this.changeAutoWirer = new ChangeAutoWirer(\n {\n getWrapperElement: () => this.wrapperElement,\n send: (message) => this.send(message),\n },\n this.logger.child(\"ChangeAutoWirer\")\n );\n\n this.webSocketManager = new WebSocketManager({\n options: this.options,\n logger: this.logger.child(\"Transport\"),\n onConnected: () => {\n this.ws = this.webSocketManager.getSocket();\n this.logger.info(\"WebSocket connected\");\n\n // Clear flash-related query params from URL to prevent stale flash on reload\n // This handles the redirect pattern: /auth?error=invalid_credentials\n this.clearFlashQueryParams();\n\n this.options.onConnect?.();\n this.wrapperElement?.dispatchEvent(new Event(\"lvt:connected\"));\n },\n onDisconnected: () => {\n this.ws = null;\n this.logger.info(\"WebSocket disconnected\");\n this.options.onDisconnect?.();\n this.wrapperElement?.dispatchEvent(new Event(\"lvt:disconnected\"));\n },\n onMessage: (response, event) => {\n this.handleWebSocketPayload(response, event);\n },\n onReconnectAttempt: () => {\n this.logger.info(\"Attempting to reconnect...\");\n },\n onError: (error) => {\n this.logger.error(\"WebSocket error:\", error);\n this.options.onError?.(error);\n },\n });\n }\n\n /**\n * Auto-initialize when DOM is ready\n * Called automatically when script loads\n */\n static autoInit(): void {\n const autoInitLogger = createLogger({ scope: \"Client:autoInit\" });\n const init = () => {\n const wrapper = document.querySelector(\"[data-lvt-id]\");\n if (wrapper) {\n const client = new LiveTemplateClient();\n client.wrapperElement = wrapper;\n\n // Check if loading indicator should be shown\n const shouldShowLoading =\n wrapper.getAttribute(\"data-lvt-loading\") === \"true\";\n if (shouldShowLoading) {\n client.loadingIndicator.show();\n client.formDisabler.disable(client.wrapperElement);\n }\n\n client.connect().catch((error) => {\n autoInitLogger.error(\"Auto-initialization connect failed:\", error);\n });\n\n // Expose as global for programmatic access\n (window as any).liveTemplateClient = client;\n }\n };\n\n // Initialize when DOM is ready\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", init);\n } else {\n init();\n }\n }\n\n /**\n * Handle server-sent updates delivered via WebSocket or HTTP fallback.\n */\n private handleWebSocketPayload(\n response: UpdateResponse,\n event?: MessageEvent<string>\n ): void {\n // Check if this is an upload-specific message\n const uploadMessage = response as any;\n if (uploadMessage.type === \"upload_progress\") {\n this.uploadHandler.handleProgressMessage(\n uploadMessage as UploadProgressMessage\n );\n return;\n }\n\n // Check if this is an upload_start response\n if (uploadMessage.upload_name && uploadMessage.entries) {\n const startResponse = uploadMessage as UploadStartResponse;\n // Handle upload start response with error handling\n try {\n this.handleUploadStartResponse(startResponse);\n } catch (error) {\n this.logger.error(\"Error handling upload start response:\", error);\n }\n return;\n }\n\n // Check if this is an upload_complete response\n if (uploadMessage.upload_name && uploadMessage.hasOwnProperty('success')) {\n // UploadCompleteResponse - just log it, no tree update needed\n if (uploadMessage.success) {\n this.logger.info(`Upload complete: ${uploadMessage.upload_name}`);\n } else {\n this.logger.error(`Upload failed: ${uploadMessage.upload_name}`, uploadMessage.error);\n }\n return;\n }\n\n if (!this.isInitialized) {\n this.loadingIndicator.hide();\n this.formDisabler.enable(this.wrapperElement);\n if (\n this.wrapperElement &&\n this.wrapperElement.hasAttribute(\"data-lvt-loading\")\n ) {\n this.wrapperElement.removeAttribute(\"data-lvt-loading\");\n }\n this.isInitialized = true;\n }\n\n if (this.wrapperElement) {\n if (response.meta?.capabilities) {\n this.changeAutoWirer.setCapabilities(response.meta.capabilities);\n }\n // Analyze statics before updateDOM so wireElements() inside updateDOM\n // has bound fields to work with. Additive: new fields from conditionally\n // rendered templates are detected as they appear in updates.\n this.changeAutoWirer.analyzeStatics(response.tree);\n\n this.updateDOM(this.wrapperElement, response.tree, response.meta);\n this.messageCount++;\n\n this.wrapperElement.dispatchEvent(\n new CustomEvent(\"lvt:updated\", {\n detail: {\n messageCount: this.messageCount,\n action: response.meta?.action,\n success: response.meta?.success,\n },\n })\n );\n }\n }\n\n /**\n * Connect to WebSocket and start receiving updates\n * @param wrapperSelector - CSS selector for the LiveTemplate wrapper (defaults to '[data-lvt-id]')\n */\n async connect(wrapperSelector: string = \"[data-lvt-id]\"): Promise<void> {\n // Find the wrapper element\n this.wrapperElement = document.querySelector(wrapperSelector);\n if (!this.wrapperElement) {\n throw new Error(\n `LiveTemplate wrapper not found with selector: ${wrapperSelector}`\n );\n }\n\n this.webSocketManager.disconnect();\n\n const connectionResult = await this.webSocketManager.connect();\n this.useHTTP = !connectionResult.usingWebSocket;\n\n if (this.useHTTP) {\n this.ws = null;\n this.logger.info(\"WebSocket not available, using HTTP mode\");\n this.options.onConnect?.();\n if (connectionResult.initialState && this.wrapperElement) {\n this.handleWebSocketPayload(connectionResult.initialState);\n }\n }\n // Set up event delegation for lvt-* attributes\n this.eventDelegator.setupEventDelegation();\n\n // Set up window-* event delegation\n this.eventDelegator.setupWindowEventDelegation();\n\n // Set up click-away delegation\n this.eventDelegator.setupClickAwayDelegation();\n\n // Set up DOM event trigger delegation for lvt-el:*:on:{event} attributes\n this.eventDelegator.setupDOMEventTriggerDelegation();\n\n // Set up click-outside listener for client-managed toast stack\n setupToastClickOutside();\n\n // Set up focus trap delegation for lvt-focus-trap attribute\n this.eventDelegator.setupFocusTrapDelegation();\n\n // Set up autofocus delegation for lvt-autofocus attribute\n this.eventDelegator.setupAutofocusDelegation();\n\n // Set up link interception for SPA navigation\n this.linkInterceptor.setup(this.wrapperElement);\n\n // Set up reactive attribute listeners for lvt-el:*:on:* attributes\n setupReactiveAttributeListeners();\n\n setupInvokerPolyfill();\n\n // Set up lifecycle listeners for lvt-fx:*:on:{lifecycle} attributes\n setupFxLifecycleListeners(this.wrapperElement);\n\n // Initialize focus tracking\n this.focusManager.attach(this.wrapperElement);\n\n // Set up infinite scroll observers\n this.observerManager.setupInfiniteScrollObserver();\n this.observerManager.setupInfiniteScrollMutationObserver();\n }\n\n /**\n * Disconnect from WebSocket\n */\n disconnect(): void {\n this.webSocketManager.disconnect();\n this.ws = null;\n this.useHTTP = false;\n this.eventDelegator.teardownDOMEventTriggerDelegation();\n if (this.wrapperElement) {\n teardownFxDOMEventTriggers(this.wrapperElement);\n teardownFxLifecycleListeners(this.wrapperElement);\n }\n this.resetSessionState();\n }\n\n // resetSessionState clears all per-session manager state. Called by both\n // disconnect() (with additional transport/event teardown) and reset().\n // Essential for cross-handler SPA navigation: without treeRenderer.reset(),\n // accumulated tree state from the old handler merges into the new one.\n //\n // Note on isInitialized: setting this to false is intentional and is\n // a behavioral change from the prior reset() which left it sticky.\n // The prior behavior was an inconsistency \u2014 \"reset\" that didn't\n // actually put the client in a pre-init state. After reset, the next\n // payload is treated as an initial render: loading indicator shows,\n // forms are enabled, data-lvt-loading is removed. This matches the\n // post-disconnect contract and the documented \"useful for testing\"\n // intent \u2014 tests can observe the init transition a second time.\n private resetSessionState(): void {\n this.treeRenderer.reset();\n this.focusManager.reset();\n this.observerManager.teardown();\n this.changeAutoWirer.teardown();\n this.formLifecycleManager.reset();\n this.loadingIndicator.hide();\n this.formDisabler.enable(this.wrapperElement);\n this.lvtId = null;\n this.isInitialized = false;\n }\n\n /**\n * Clear flash-related query parameters (error, success) from the URL.\n * This prevents stale flash messages from reappearing on page reload.\n * Uses history.replaceState to update URL without triggering navigation.\n */\n private clearFlashQueryParams(): void {\n const url = new URL(window.location.href);\n const flashParams = [\"error\", \"success\"];\n let hasFlashParams = false;\n\n for (const param of flashParams) {\n if (url.searchParams.has(param)) {\n url.searchParams.delete(param);\n hasFlashParams = true;\n }\n }\n\n if (hasFlashParams) {\n this.logger.debug(\"Clearing flash query params from URL\");\n window.history.replaceState(null, \"\", url.toString());\n }\n }\n\n /**\n * Determine whether the client finished its initial load and has an active transport.\n */\n isReady(): boolean {\n const wrapper = this.wrapperElement;\n\n if (!wrapper || wrapper.hasAttribute(\"data-lvt-loading\")) {\n return false;\n }\n\n if (this.useHTTP) {\n return true;\n }\n\n const readyState = this.webSocketManager.getReadyState();\n return readyState === 1; // WebSocket.OPEN = 1\n }\n\n /**\n * Send a message to the server via WebSocket or HTTP\n * @param message - Message to send (will be JSON stringified)\n */\n send(message: any): void {\n const readyState = this.webSocketManager.getReadyState();\n\n if (this.logger.isDebugEnabled()) {\n this.logger.debug(\"send() invoked\", {\n message,\n useHTTP: this.useHTTP,\n hasWebSocket: readyState !== undefined,\n readyState,\n });\n }\n\n if (this.useHTTP) {\n this.logger.debug(\"Using HTTP mode for send\");\n this.sendHTTP(message);\n } else if (readyState === 1) { // WebSocket.OPEN = 1\n this.logger.debug(\"Sending via WebSocket\");\n this.webSocketManager.send(JSON.stringify(message));\n } else if (readyState !== undefined) {\n this.logger.warn(\n `WebSocket not ready (state: ${readyState}), using HTTP fallback`\n );\n this.sendHTTP(message);\n } else {\n this.logger.error(\"No transport available\");\n }\n }\n\n /**\n * Get the current live URL for HTTP methods. Falls back to\n * options.liveUrl when no override is set. Cross-handler navigation\n * uses setLiveUrl() to update the override without mutating options.\n */\n private getLiveUrl(): string {\n return this.liveUrlOverride || this.options.liveUrl || \"/live\";\n }\n\n /**\n * Send an in-band navigate message over the existing WebSocket.\n *\n * This is the client side of the same-handler SPA navigation flow.\n * Rather than disconnect + reconnect to land a URL change (which is\n * what cross-handler nav does and what the old same-handler path\n * silently skipped), we parse the target URL's query params into a\n * data map and send {action: \"__navigate__\", data: params}. The\n * server special-cases this action name in its event loop (see\n * livetemplate/mount.go) and re-runs Mount with the new data without\n * tearing down the connection.\n *\n * Equivalent to Phoenix LiveView's live_patch / handle_params:\n * path-level identity for the socket, Mount-level re-projection for\n * query-string changes.\n *\n * @param href - The target URL to navigate to. Only the search params\n * are consumed; the pathname is assumed to match the\n * current page (caller checks same-handler first).\n */\n private sendNavigate(href: string): boolean {\n const url = new URL(href, window.location.origin);\n const data: Record<string, string> = {};\n // Note: duplicate keys (e.g. ?tag=a&tag=b) are last-write-wins here.\n // LiveTemplate routes use scalar string params by convention. Routes\n // that need repeated params should not use sendNavigate directly.\n const seenKeys = new Set<string>();\n url.searchParams.forEach((v, k) => {\n if (seenKeys.has(k)) {\n this.logger.warn(\"sendNavigate: duplicate query param key \u2014 last value wins; server may receive incomplete data\", { key: k, href });\n }\n seenKeys.add(k);\n data[k] = v;\n });\n\n const newLiveUrl = url.pathname + url.search;\n\n // __navigate__ is a WebSocket-only in-band action \u2014 only call send()\n // when the socket is actually OPEN.\n //\n // Note: this.useHTTP is not checked here because sendNavigate() is only\n // reachable when canSendNavigate() returns true (i.e. !this.useHTTP and\n // readyState === 1), enforced by LinkInterceptor. Checking useHTTP or\n // re-checking readyState here would be dead code in the normal call path.\n // The guard below is a defensive fallback for direct callers that bypass\n // canSendNavigate().\n if (this.webSocketManager.getReadyState() !== 1 /* WebSocket.OPEN */) {\n const readyState = this.webSocketManager.getReadyState();\n if (readyState === 3 /* CLOSED */) {\n this.logger.error(\n \"sendNavigate: WebSocket is CLOSED and autoReconnect may be disabled; \" +\n \"navigate message dropped. Reload or re-enable autoReconnect.\",\n { href }\n );\n } else {\n // CONNECTING (0) or CLOSING (2). autoReconnect defaults to false.\n if (!this.options.autoReconnect) {\n this.logger.error(\n \"sendNavigate: WS not open and autoReconnect is disabled; navigate may be permanently lost\",\n { href, readyState }\n );\n } else {\n this.logger.warn(\n \"sendNavigate: WS not open; browser URL is ahead of server state until reconnect\",\n { href, readyState }\n );\n }\n }\n return false;\n }\n\n // Socket is OPEN: commit the URL update and send the navigate message.\n // liveUrlOverride is updated here (not before the guard) so it only\n // advances when the message is actually sent \u2014 keeping it consistent\n // with window.location throughout.\n this.liveUrlOverride = newLiveUrl;\n this.webSocketManager.setLiveUrl(newLiveUrl);\n this.logger.debug(\"sendNavigate\", { href, data });\n this.send({ action: \"__navigate__\", data });\n return true;\n }\n\n /**\n * Send action via HTTP POST\n */\n private async sendHTTP(message: any): Promise<void> {\n try {\n const liveUrl = this.getLiveUrl();\n const response = await fetch(liveUrl, {\n method: \"POST\",\n credentials: \"include\", // Include cookies for session\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: JSON.stringify(message),\n });\n\n if (!response.ok) {\n throw new Error(`HTTP request failed: ${response.status}`);\n }\n\n // Handle the update response\n const updateResponse: UpdateResponse = await response.json();\n if (this.wrapperElement) {\n this.updateDOM(\n this.wrapperElement,\n updateResponse.tree,\n updateResponse.meta\n );\n }\n } catch (error) {\n this.logger.error(\"Failed to send HTTP request:\", error);\n }\n }\n\n /**\n * Send form with file inputs via HTTP POST multipart/form-data.\n * Used for Tier 1 file uploads where binary files are submitted via\n * HTTP fetch instead of WebSocket (avoids base64 encoding overhead).\n *\n * IMPORTANT: Callers must pass pre-captured form data (formData).\n * FormData must be built BEFORE setActiveSubmission disables the\n * form's fieldset \u2014 otherwise FormData would be empty because\n * disabled fieldsets exclude all child fields. The caller is also\n * responsible for setting the \"lvt-action\" entry; this method will\n * not mutate the passed FormData.\n */\n sendHTTPMultipart(form: HTMLFormElement, action: string, formData: FormData): void {\n this.doSendHTTPMultipart(form, action, formData);\n }\n\n private async doSendHTTPMultipart(\n form: HTMLFormElement,\n action: string,\n formData: FormData\n ): Promise<void> {\n try {\n const liveUrl = this.getLiveUrl();\n\n const response = await fetch(liveUrl, {\n method: \"POST\",\n credentials: \"include\",\n headers: {\n Accept: \"application/json\",\n // Do NOT set Content-Type \u2014 browser sets multipart boundary automatically\n },\n body: formData,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP multipart request failed: ${response.status}`);\n }\n\n const updateResponse: UpdateResponse = await response.json();\n if (this.wrapperElement) {\n this.updateDOM(\n this.wrapperElement,\n updateResponse.tree,\n updateResponse.meta\n );\n }\n } catch (error) {\n this.logger.error(\"Failed to send HTTP multipart request:\", error);\n }\n }\n\n /**\n * Handle navigation response from link interception.\n * Extracts the wrapper content from the full HTML page and replaces\n * the current wrapper content. Content comes from same-origin fetch\n * responses only (link interceptor skips external origins).\n *\n * Supports both same-handler navigation (same data-lvt-id) and\n * cross-handler navigation (different data-lvt-id). Cross-handler\n * navigation disconnects the old WebSocket, replaces the wrapper\n * content and ID, and reconnects to the new handler. The URL must\n * already be updated via pushState before this method is called\n * (done in LinkInterceptor.navigate) so the WebSocket connects to\n * the correct handler.\n */\n private handleNavigationResponse(html: string): void {\n if (!this.wrapperElement) return;\n\n const parser = new DOMParser();\n const doc = parser.parseFromString(html, \"text/html\");\n const oldId = this.wrapperElement.getAttribute(\"data-lvt-id\");\n\n // Update document title from the fetched page. Only apply if the\n // new title is non-empty \u2014 blanking the title would be surprising\n // and unhelpful.\n const newTitleText = doc.querySelector(\"title\")?.textContent;\n if (newTitleText) {\n document.title = newTitleText;\n }\n\n // sameWrapper: the fetched page has the same data-lvt-id as the current\n // wrapper. This means two different paths share the same handler ID.\n // handleNavigationResponse is only reached via LinkInterceptor for\n // cross-pathname navigations (same-pathname links are caught by the fast\n // path before a fetch is issued and handled via sendNavigate directly).\n // For cross-pathname same-ID, a full reconnect is correct: the server\n // must receive the new URL to re-run Mount on the right route. We fall\n // through to the newWrapper block below, which handles both same-ID and\n // different-ID handler switches identically.\n //\n // Same-pathname same-ID navigation (query-param change on the same route)\n // is covered exclusively by LinkInterceptor's fast path and navigate.test.ts.\n\n // Check for any handler wrapper (same-ID cross-path or different handler)\n const newWrapper = doc.querySelector(\"[data-lvt-id]\");\n if (newWrapper) {\n const newId = newWrapper.getAttribute(\"data-lvt-id\");\n // Guard: attribute exists (we queried by [data-lvt-id]) but could be empty\n if (!newId) {\n this.logger.warn(\"Cross-handler navigation: new wrapper has empty data-lvt-id\");\n window.location.reload();\n return;\n }\n\n // Clean up stale event listeners keyed to the old wrapper ID.\n // Each component knows its own listener keys, so we delegate.\n this.linkInterceptor.teardownForWrapper(oldId);\n this.eventDelegator.teardownForWrapper(oldId);\n\n // Supersede any previous in-flight connect() from an earlier navigation\n const myEpoch = ++this.navigationEpoch;\n\n this.disconnect();\n this.wrapperElement.setAttribute(\"data-lvt-id\", newId);\n this.wrapperElement.replaceChildren(\n ...Array.from(newWrapper.childNodes).map((n) => n.cloneNode(true))\n );\n\n // Set up event delegation and link interception immediately so the\n // new content has working listeners BEFORE the async connect() runs.\n // connect() will re-run these internally, which is safe: both setup\n // methods are idempotent \u2014 they remove any existing listener with\n // the same key before adding the new one. Calling them twice in\n // quick succession results in a single active listener per event\n // type per wrapper ID.\n this.eventDelegator.setupEventDelegation();\n this.linkInterceptor.setup(this.wrapperElement);\n\n // Scroll to top for cross-handler navigation\n window.scrollTo(0, 0);\n\n // Update the live URL used by HTTP methods AND the WebSocket\n // manager to derive the reconnect path. We use private overrides\n // on both so the caller-provided options object is never mutated.\n // Hash fragments are intentionally excluded \u2014 the WebSocket path\n // comes from pathname+search only.\n //\n // liveUrl convention: it is always the CURRENT PAGE PATH, not a\n // separate endpoint. Each LiveTemplate handler route is both the\n // HTTP page and the WebSocket endpoint for that handler, so the\n // page path and the WebSocket path are always the same. Apps that\n // need a different WebSocket endpoint should set `wsUrl`, which\n // takes precedence over `liveUrl` in WebSocketManager.\n //\n // Invariant: link-interceptor.ts calls pushState BEFORE invoking\n // handleNavigationResponse, so window.location here always\n // reflects the final target URL (not the previous one). This is\n // why liveUrlOverride is never stale for cross-pathname navigations\n // even if sendNavigate had previously set it to a same-pathname URL.\n const newLiveUrl =\n window.location.pathname + window.location.search;\n this.liveUrlOverride = newLiveUrl;\n this.webSocketManager.setLiveUrl(newLiveUrl);\n\n // In HTTP mode there is no persistent WebSocket connection \u2014 skip\n // connect() so we don't unexpectedly create a WebSocket. The DOM swap\n // and event re-setup above are sufficient for HTTP-mode apps; the next\n // user action will POST via the normal HTTP send path.\n if (!this.useHTTP) {\n // Reconnect to the new handler. The server sends an initial tree\n // that produces the same DOM as the fetched HTML.\n //\n // Escape the wrapper ID to defend against (pathological) server\n // responses with special characters that would break the\n // attribute selector. Only `\"` and `\\` need escaping inside a\n // double-quoted attribute value selector (`[attr=\"...\"]`), and\n // we prefer a manual escape over CSS.escape() which is not\n // available in jsdom test environments.\n //\n // Epoch semantics: the failure branch is guarded by the epoch\n // check to avoid stale reloads. The success branch has no work\n // to do \u2014 there's nothing for handleNavigationResponse to undo\n // on success.\n //\n // Known limitation: if two cross-handler navigations run in rapid\n // succession (A then B), A's connect() might still be executing\n // its post-await setup (useHTTP assignment, initial state\n // rendering, event delegation) when B starts. Because there's\n // only one WebSocketManager transport at a time, B's disconnect()\n // kills A's in-flight transport, and B's setup happens on the\n // wrapper with B's ID. If A's post-await code runs AFTER B sets\n // the wrapper ID, A's querySelector lookup would already be\n // stale (it captured the wrapper synchronously before the await).\n // A true fix requires making connect() itself cancellable with\n // an AbortSignal, which is out of scope for this PR. In practice,\n // two successive SPA navigations within a single event loop tick\n // are rare, and the idempotent setup methods minimize fallout.\n const escapedId = newId.replace(/[\\\\\"]/g, \"\\\\$&\");\n const selector = `[data-lvt-id=\"${escapedId}\"]`;\n this.connect(selector).catch((err) => {\n if (myEpoch !== this.navigationEpoch) return;\n this.logger.error(\"Cross-handler reconnect failed:\", err);\n window.location.reload();\n });\n }\n return;\n }\n\n // Non-LiveTemplate page \u2014 disconnect the old WebSocket (it's pointing\n // to a handler whose DOM is about to be replaced) and tear down the\n // old listeners keyed to the previous wrapper ID, then use body\n // content fallback.\n this.linkInterceptor.teardownForWrapper(oldId);\n this.eventDelegator.teardownForWrapper(oldId);\n this.disconnect();\n const body = doc.querySelector(\"body\");\n if (body) {\n this.wrapperElement.replaceChildren(\n ...Array.from(body.childNodes).map((n) => n.cloneNode(true))\n );\n }\n this.eventDelegator.setupEventDelegation();\n this.linkInterceptor.setup(this.wrapperElement);\n }\n\n /**\n * Parse a string value into appropriate type (number, boolean, or string)\n * @param value - String value to parse\n * @returns Parsed value with correct type\n */\n private parseValue(value: string): any {\n // Trim once for consistent handling\n const trimmed = value.trim();\n\n // Try to parse as number\n const num = parseFloat(trimmed);\n if (!isNaN(num)) {\n // Check range FIRST - large integers (like UnixNano timestamps) must stay as strings\n // to preserve precision. JavaScript's Number can only safely represent integers\n // up to 2^53-1 (MAX_SAFE_INTEGER = 9,007,199,254,740,991).\n if (Number.isInteger(num) && Math.abs(num) > Number.MAX_SAFE_INTEGER) {\n return trimmed;\n }\n // Only convert to number if string representation matches (no precision loss)\n if (trimmed === num.toString()) {\n return num;\n }\n }\n\n // Try to parse as boolean\n if (value === \"true\") return true;\n if (value === \"false\") return false;\n\n // Return as string\n return value;\n }\n\n /**\n * Apply an update to the current state and reconstruct HTML\n * @param update - Tree update object from LiveTemplate server\n * @returns Reconstructed HTML and whether anything changed\n */\n applyUpdate(update: TreeNode): UpdateResult {\n return this.treeRenderer.applyUpdate(update);\n }\n\n /**\n * Apply updates to existing HTML using morphdom for efficient DOM updates\n * @param existingHTML - Current full HTML document\n * @param update - Tree update object from LiveTemplate server\n * @returns Updated HTML content\n */\n applyUpdateToHTML(existingHTML: string, update: TreeNode): string {\n // Apply the update to our internal state\n const result = this.applyUpdate(update);\n\n // Extract lvt-id from existing HTML if we don't have it\n if (!this.lvtId) {\n const match = existingHTML.match(/data-lvt-id=\"([^\"]+)\"/);\n if (match) {\n this.lvtId = match[1];\n }\n }\n\n // The new tree includes complete HTML structure, so we can reconstruct properly\n const innerContent = result.html;\n\n // Find where to insert the reconstructed content\n const bodyMatch = existingHTML.match(/<body>([\\s\\S]*?)<\\/body>/);\n if (!bodyMatch) {\n return existingHTML;\n }\n\n // Replace the body content with our reconstructed HTML\n // We need to preserve the wrapper div with data-lvt-id\n const wrapperStart = `<div data-lvt-id=\"${this.lvtId || \"lvt-unknown\"}\">`;\n const wrapperEnd = \"</div>\";\n const newBodyContent = wrapperStart + innerContent + wrapperEnd;\n\n return existingHTML.replace(\n /<body>[\\s\\S]*?<\\/body>/,\n `<body>${newBodyContent}</body>`\n );\n }\n\n /**\n * Update a live DOM element with new tree data\n * @param element - DOM element containing the LiveTemplate content (the wrapper div)\n * @param update - Tree update object from LiveTemplate server\n * @param meta - Optional metadata about the update (action, success, errors)\n */\n updateDOM(element: Element, update: TreeNode, meta?: ResponseMetadata): void {\n // Apply update to internal state and get reconstructed HTML\n const result = this.applyUpdate(update);\n\n // Helper to recursively check if there are any statics in the tree\n const hasStaticsInTree = (node: any): boolean => {\n if (!node || typeof node !== \"object\") return false;\n if (node.s && Array.isArray(node.s)) return true;\n return Object.values(node).some((v) => hasStaticsInTree(v));\n };\n\n if (!result.changed && !hasStaticsInTree(update)) {\n // No changes detected and no statics in update, skip morphdom\n return;\n }\n\n // Create a temporary wrapper to hold the new content\n // We need to create a DOM element of the same type as 'element' to avoid browser HTML corrections\n // For example, if we put <tr> elements in a <div>, the browser strips them out\n const tempWrapper = document.createElement(element.tagName);\n\n if (this.logger.isDebugEnabled()) {\n this.logger.debug(\"[updateDOM] element.tagName:\", element.tagName);\n this.logger.debug(\n \"[updateDOM] result.html (first 500 chars):\",\n result.html.substring(0, 500)\n );\n this.logger.debug(\n \"[updateDOM] Has <table> tag:\",\n result.html.includes(\"<table>\")\n );\n this.logger.debug(\n \"[updateDOM] Has <tbody> tag:\",\n result.html.includes(\"<tbody>\")\n );\n this.logger.debug(\n \"[updateDOM] Has <tr> tag:\",\n result.html.includes(\"<tr\")\n );\n }\n\n // Use DOMParser when the HTML contains <script> tags. Browsers'\n // innerHTML parser handles scripts specially and can create phantom\n // duplicate DOM nodes after the closing tag. DOMParser doesn't have\n // this quirk because it returns a standalone document.\n //\n // Regex /<script[\\s>]/i is more precise than a bare \"<script\" string\n // match: it avoids false positives from words ending in \"script\" that\n // aren't a tag (e.g. \"noscript\"), while still matching <script>,\n // <script type=\"...\"> and <SCRIPT> case-insensitively.\n // Note: it can still match <script inside attribute values or HTML\n // comments \u2014 a false positive is harmless (DOMParser is always safe;\n // we just pay a small allocation cost for the parse).\n //\n // Wrap with the same tagName as the target element (not a hard-coded\n // <div>) so that DOMParser applies the correct HTML parsing rules.\n // Wrapping <tr>/<td>/<option> content in a <div> can trigger\n // browser re-parenting; using the real container tag avoids that.\n if (/<script[\\s>]/i.test(result.html)) {\n // Guard: <body> and <html> cannot be used as wrapper tags for\n // parseFromString \u2014 doc.body.firstElementChild would return the\n // first child of body, not the body itself, discarding the wrap.\n // Fall back to <div> for these edge cases; updateDOM is called on\n // the lvt wrapper div in practice, so this branch is defensive.\n const rawTag = element.tagName.toLowerCase();\n const wrapTag = (rawTag === \"body\" || rawTag === \"html\") ? \"div\" : rawTag;\n const parser = new DOMParser();\n const doc = parser.parseFromString(\n `<${wrapTag}>${result.html}</${wrapTag}>`,\n \"text/html\"\n );\n const root = doc.body.firstElementChild;\n if (root) {\n // Array.from snapshots the live NodeList before replaceChildren\n // starts moving nodes, keeping iteration stable.\n //\n // Note: DOMParser still re-parents bare table-cell content (tr/td\n // without surrounding table+tbody) even when wrapTag matches.\n // Slots rendered into table-cell elements with <script> tags are\n // an edge case; a follow-up can add a full-table wrapper for those.\n tempWrapper.replaceChildren(...Array.from(root.childNodes));\n } else {\n // root is null when the HTML parser produced no element child for\n // our wrapper tag (e.g. the wrapper was itself re-parented or the\n // fragment is text-only). Fall back to doc.body children \u2014 the\n // content is still present there, already parsed by DOMParser\n // without the script-duplication quirk that innerHTML triggers.\n this.logger.warn(\"[updateDOM] DOMParser: no wrapper root element; using doc.body children\");\n tempWrapper.replaceChildren(...Array.from(doc.body.childNodes));\n }\n } else {\n tempWrapper.innerHTML = result.html;\n }\n\n if (this.logger.isDebugEnabled()) {\n this.logger.debug(\n \"[updateDOM] tempWrapper.innerHTML (first 500 chars):\",\n tempWrapper.innerHTML.substring(0, 500)\n );\n this.logger.debug(\n \"[updateDOM] tempWrapper has <table>:\",\n tempWrapper.innerHTML.includes(\"<table>\")\n );\n this.logger.debug(\n \"[updateDOM] tempWrapper has <tbody>:\",\n tempWrapper.innerHTML.includes(\"<tbody>\")\n );\n this.logger.debug(\n \"[updateDOM] tempWrapper has <tr>:\",\n tempWrapper.innerHTML.includes(\"<tr\")\n );\n }\n\n // Use morphdom to efficiently update the element\n morphdom(element, tempWrapper, {\n childrenOnly: true, // Only update children, preserve the wrapper element itself\n getNodeKey: (node: any) => {\n // Use data-key or data-lvt-key for efficient reconciliation\n if (node.nodeType === 1) {\n return (\n node.getAttribute(\"data-key\") ||\n node.getAttribute(\"data-lvt-key\") ||\n undefined\n );\n }\n },\n onBeforeElUpdated: (fromEl, toEl) => {\n // Honour lvt-preserve: the client owns this element's entire\n // subtree and morphdom should never touch any of it.\n // Attributes, content, descendants all stay exactly as the DOM\n // currently has them. Equivalent to Phoenix LiveView's\n // phx-update=\"ignore\" and Turbo's data-turbo-permanent. Useful\n // for third-party widgets (maps, date pickers, charts) that\n // mutate their own DOM, and for scroll containers with\n // JS-managed scrollTop.\n //\n // We check toEl (the incoming server version) rather than fromEl\n // (the current DOM). This gives the server authority: if a later\n // render omits lvt-preserve, morphdom resumes updating the element.\n // Checking fromEl would make the attribute sticky in the DOM\n // forever once applied, preventing the server from ever removing it.\n //\n // For the subtler case of \"preserve my *attributes* but still\n // let children update\" (e.g. <details open>, <select> with\n // user-selected options), use lvt-preserve-attrs below.\n //\n // Priority: lvt-preserve is checked first and returns early; if\n // toEl has lvt-preserve, lvt-preserve-attrs on the same element\n // is never reached. lvt-preserve wins (full freeze > partial\n // attribute-only freeze).\n if (\n toEl.nodeType === Node.ELEMENT_NODE &&\n (toEl as Element).hasAttribute(\"lvt-preserve\")\n ) {\n return false;\n }\n\n // Honour lvt-preserve-attrs: the client owns this element's own\n // attributes (e.g. the <details open> state the user toggled),\n // but its children should still be diffed against the new tree.\n // This is the right fit for collapsible pickers where the\n // *container* state is user-managed but the *items* inside are\n // server-authored and must reflect the latest data.\n //\n // Implementation: copy fromEl's attributes that are missing from\n // toEl onto toEl before morphdom runs its attribute diff. This\n // makes morphdom see the user-owned attrs as \"already present\"\n // in the new version, so it doesn't remove them. Attributes that\n // exist in both fromEl and toEl take toEl's value (server wins\n // over client for attributes the template does control), which\n // preserves the ability to update className etc. on the same\n // element from the server.\n //\n // Limitation: only *missing* attributes from fromEl are copied.\n // If the server's toEl already has e.g. class=\"card\", that value\n // is used \u2014 JS-added classes (el.classList.add('open')) are\n // silently overwritten on the next diff. Use lvt-preserve-attrs\n // for attributes the server template does NOT set at all.\n //\n // We check toEl (the incoming server tree), not fromEl (the live\n // DOM), so the server has authority: removing lvt-preserve-attrs\n // from the template takes effect immediately on the next render,\n // consistent with how lvt-preserve works.\n if (\n toEl.nodeType === Node.ELEMENT_NODE &&\n (toEl as Element).hasAttribute(\"lvt-preserve-attrs\") &&\n fromEl.nodeType === Node.ELEMENT_NODE\n ) {\n const fromAttrs = (fromEl as Element).attributes;\n const toElement = toEl as Element;\n for (let i = 0; i < fromAttrs.length; i++) {\n const attr = fromAttrs[i];\n // Skip the preserve control attributes themselves so the\n // server can opt elements in/out across renders. If we\n // copied lvt-preserve-attrs back onto toEl, the server\n // could never remove the attribute in a future update.\n if (\n attr.name === \"lvt-preserve\" ||\n attr.name === \"lvt-preserve-attrs\"\n ) {\n continue;\n }\n // Use hasAttributeNS / setAttributeNS to preserve namespace\n // info (e.g. xlink:href on SVG elements). For plain HTML\n // attributes namespaceURI is null, so this degrades to the\n // same behaviour as setAttribute.\n if (!toElement.hasAttributeNS(attr.namespaceURI, attr.localName)) {\n toElement.setAttributeNS(attr.namespaceURI, attr.name, attr.value);\n }\n }\n // Fall through to normal diff path so children are still updated.\n }\n\n // Skip update entirely for focused form elements to preserve user\n // input. This also skips attribute updates (class, disabled, aria-*)\n // and the lvt-updated hook \u2014 use data-lvt-force-update to override.\n if (this.focusManager.shouldSkipUpdate(fromEl)) {\n return false;\n }\n\n // Only update if content actually changed\n if (fromEl.isEqualNode(toEl)) {\n return false;\n }\n // Execute lvt-updated lifecycle hook\n this.executeLifecycleHook(fromEl, \"lvt-updated\");\n return true;\n },\n onElUpdated: (el) => {\n // Textarea-specific: morphdom patches child text nodes but browsers\n // ignore textContent changes to \"dirty\" textareas (ones the user\n // has typed in), so we explicitly set .value. Inputs don't need\n // this \u2014 morphdom sets .value directly for input elements.\n if (el instanceof HTMLTextAreaElement) {\n el.value = el.textContent ?? \"\";\n }\n },\n onNodeAdded: (node) => {\n // Sync textarea value for newly inserted textarea elements\n if (node instanceof HTMLTextAreaElement) {\n node.value = node.textContent ?? \"\";\n }\n // Execute lvt-mounted lifecycle hook\n if (node.nodeType === Node.ELEMENT_NODE) {\n this.executeLifecycleHook(node as Element, \"lvt-mounted\");\n }\n },\n onBeforeNodeDiscarded: (node) => {\n // Execute lvt-destroyed lifecycle hook\n if (node.nodeType === Node.ELEMENT_NODE) {\n this.executeLifecycleHook(node as Element, \"lvt-destroyed\");\n }\n return true;\n },\n });\n\n // Restore focus to previously focused element\n this.focusManager.restoreFocusedElement();\n\n // Handle scroll directives (implicit trigger only)\n handleScrollDirectives(element);\n\n // Handle highlight directives (implicit trigger only)\n handleHighlightDirectives(element);\n\n // Handle animate directives (implicit trigger only)\n handleAnimateDirectives(element);\n\n // Set up DOM event triggers for lvt-fx: attributes with :on:{event}\n // Registry always lives on wrapperElement so teardown can find all entries\n setupFxDOMEventTriggers(element, this.wrapperElement || undefined);\n\n // Re-scan updated subtree for lvt-el:*:on:{event} DOM triggers\n this.eventDelegator.setupDOMEventTriggerDelegation(element);\n\n // Handle toast trigger directives (ephemeral client-side toasts)\n handleToastDirectives(element);\n\n // Initialize upload file inputs\n this.uploadHandler.initializeFileInputs(element);\n\n // Auto-wire change listeners for bound form fields\n this.changeAutoWirer.wireElements();\n\n // Handle form lifecycle if metadata is present\n if (meta) {\n this.formLifecycleManager.handleResponse(meta);\n }\n }\n\n /**\n * Handle upload_start response from server\n */\n private handleUploadStartResponse(response: UploadStartResponse): void {\n this.uploadHandler.handleUploadStartResponse(response);\n }\n\n /**\n * Execute lifecycle hook on an element\n * @param element - Element with lifecycle hook attribute\n * @param hookName - Name of the lifecycle hook attribute (e.g., 'lvt-mounted')\n */\n private executeLifecycleHook(element: Element, hookName: string): void {\n const hookValue = element.getAttribute(hookName);\n if (!hookValue) {\n return;\n }\n\n try {\n // Create a function from the hook value and execute it\n // The function has access to 'this' (the element) and 'event'\n const hookFunction = new Function(\"element\", hookValue);\n hookFunction.call(element, element);\n } catch (error) {\n this.logger.error(`Error executing ${hookName} hook:`, error);\n }\n }\n\n /**\n * Reset client state (useful for testing).\n *\n * Puts the client back into its pre-initialization state: tree state,\n * focus state, observers, change auto-wirer, form lifecycle, loading\n * indicator, form disabler, lvtId, AND isInitialized are all cleared.\n *\n * Behavioral note: `isInitialized` is set to false here. Prior to the\n * introduction of `resetSessionState()`, `reset()` left this flag sticky,\n * which was an inconsistency \u2014 a \"reset\" that didn't actually put the\n * client in a pre-init state. After calling reset(), the next payload\n * is treated as an initial render: the loading indicator will briefly\n * appear, forms are re-enabled, and `data-lvt-loading` is cleared. If\n * callers of reset() expected the prior sticky behavior, they should\n * not rely on init-only side effects firing exactly once per client\n * lifetime.\n */\n reset(): void {\n this.resetSessionState();\n }\n\n /**\n * Get current tree state (for debugging)\n */\n getTreeState(): TreeNode {\n return this.treeRenderer.getTreeState();\n }\n\n /**\n * Get the static structure if available\n */\n getStaticStructure(): string[] | null {\n return this.treeRenderer.getStaticStructure();\n }\n}\n\n// Auto-initialize when script loads (for browser use)\nif (typeof window !== \"undefined\") {\n LiveTemplateClient.autoInit();\n}\n", "var DOCUMENT_FRAGMENT_NODE = 11;\n\nfunction morphAttrs(fromNode, toNode) {\n var toNodeAttrs = toNode.attributes;\n var attr;\n var attrName;\n var attrNamespaceURI;\n var attrValue;\n var fromValue;\n\n // document-fragments dont have attributes so lets not do anything\n if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE || fromNode.nodeType === DOCUMENT_FRAGMENT_NODE) {\n return;\n }\n\n // update attributes on original DOM element\n for (var i = toNodeAttrs.length - 1; i >= 0; i--) {\n attr = toNodeAttrs[i];\n attrName = attr.name;\n attrNamespaceURI = attr.namespaceURI;\n attrValue = attr.value;\n\n if (attrNamespaceURI) {\n attrName = attr.localName || attrName;\n fromValue = fromNode.getAttributeNS(attrNamespaceURI, attrName);\n\n if (fromValue !== attrValue) {\n if (attr.prefix === 'xmlns'){\n attrName = attr.name; // It's not allowed to set an attribute with the XMLNS namespace without specifying the `xmlns` prefix\n }\n fromNode.setAttributeNS(attrNamespaceURI, attrName, attrValue);\n }\n } else {\n fromValue = fromNode.getAttribute(attrName);\n\n if (fromValue !== attrValue) {\n fromNode.setAttribute(attrName, attrValue);\n }\n }\n }\n\n // Remove any extra attributes found on the original DOM element that\n // weren't found on the target element.\n var fromNodeAttrs = fromNode.attributes;\n\n for (var d = fromNodeAttrs.length - 1; d >= 0; d--) {\n attr = fromNodeAttrs[d];\n attrName = attr.name;\n attrNamespaceURI = attr.namespaceURI;\n\n if (attrNamespaceURI) {\n attrName = attr.localName || attrName;\n\n if (!toNode.hasAttributeNS(attrNamespaceURI, attrName)) {\n fromNode.removeAttributeNS(attrNamespaceURI, attrName);\n }\n } else {\n if (!toNode.hasAttribute(attrName)) {\n fromNode.removeAttribute(attrName);\n }\n }\n }\n}\n\nvar range; // Create a range object for efficently rendering strings to elements.\nvar NS_XHTML = 'http://www.w3.org/1999/xhtml';\n\nvar doc = typeof document === 'undefined' ? undefined : document;\nvar HAS_TEMPLATE_SUPPORT = !!doc && 'content' in doc.createElement('template');\nvar HAS_RANGE_SUPPORT = !!doc && doc.createRange && 'createContextualFragment' in doc.createRange();\n\nfunction createFragmentFromTemplate(str) {\n var template = doc.createElement('template');\n template.innerHTML = str;\n return template.content.childNodes[0];\n}\n\nfunction createFragmentFromRange(str) {\n if (!range) {\n range = doc.createRange();\n range.selectNode(doc.body);\n }\n\n var fragment = range.createContextualFragment(str);\n return fragment.childNodes[0];\n}\n\nfunction createFragmentFromWrap(str) {\n var fragment = doc.createElement('body');\n fragment.innerHTML = str;\n return fragment.childNodes[0];\n}\n\n/**\n * This is about the same\n * var html = new DOMParser().parseFromString(str, 'text/html');\n * return html.body.firstChild;\n *\n * @method toElement\n * @param {String} str\n */\nfunction toElement(str) {\n str = str.trim();\n if (HAS_TEMPLATE_SUPPORT) {\n // avoid restrictions on content for things like `<tr><th>Hi</th></tr>` which\n // createContextualFragment doesn't support\n // <template> support not available in IE\n return createFragmentFromTemplate(str);\n } else if (HAS_RANGE_SUPPORT) {\n return createFragmentFromRange(str);\n }\n\n return createFragmentFromWrap(str);\n}\n\n/**\n * Returns true if two node's names are the same.\n *\n * NOTE: We don't bother checking `namespaceURI` because you will never find two HTML elements with the same\n * nodeName and different namespace URIs.\n *\n * @param {Element} a\n * @param {Element} b The target element\n * @return {boolean}\n */\nfunction compareNodeNames(fromEl, toEl) {\n var fromNodeName = fromEl.nodeName;\n var toNodeName = toEl.nodeName;\n var fromCodeStart, toCodeStart;\n\n if (fromNodeName === toNodeName) {\n return true;\n }\n\n fromCodeStart = fromNodeName.charCodeAt(0);\n toCodeStart = toNodeName.charCodeAt(0);\n\n // If the target element is a virtual DOM node or SVG node then we may\n // need to normalize the tag name before comparing. Normal HTML elements that are\n // in the \"http://www.w3.org/1999/xhtml\"\n // are converted to upper case\n if (fromCodeStart <= 90 && toCodeStart >= 97) { // from is upper and to is lower\n return fromNodeName === toNodeName.toUpperCase();\n } else if (toCodeStart <= 90 && fromCodeStart >= 97) { // to is upper and from is lower\n return toNodeName === fromNodeName.toUpperCase();\n } else {\n return false;\n }\n}\n\n/**\n * Create an element, optionally with a known namespace URI.\n *\n * @param {string} name the element name, e.g. 'div' or 'svg'\n * @param {string} [namespaceURI] the element's namespace URI, i.e. the value of\n * its `xmlns` attribute or its inferred namespace.\n *\n * @return {Element}\n */\nfunction createElementNS(name, namespaceURI) {\n return !namespaceURI || namespaceURI === NS_XHTML ?\n doc.createElement(name) :\n doc.createElementNS(namespaceURI, name);\n}\n\n/**\n * Copies the children of one DOM element to another DOM element\n */\nfunction moveChildren(fromEl, toEl) {\n var curChild = fromEl.firstChild;\n while (curChild) {\n var nextChild = curChild.nextSibling;\n toEl.appendChild(curChild);\n curChild = nextChild;\n }\n return toEl;\n}\n\nfunction syncBooleanAttrProp(fromEl, toEl, name) {\n if (fromEl[name] !== toEl[name]) {\n fromEl[name] = toEl[name];\n if (fromEl[name]) {\n fromEl.setAttribute(name, '');\n } else {\n fromEl.removeAttribute(name);\n }\n }\n}\n\nvar specialElHandlers = {\n OPTION: function(fromEl, toEl) {\n var parentNode = fromEl.parentNode;\n if (parentNode) {\n var parentName = parentNode.nodeName.toUpperCase();\n if (parentName === 'OPTGROUP') {\n parentNode = parentNode.parentNode;\n parentName = parentNode && parentNode.nodeName.toUpperCase();\n }\n if (parentName === 'SELECT' && !parentNode.hasAttribute('multiple')) {\n if (fromEl.hasAttribute('selected') && !toEl.selected) {\n // Workaround for MS Edge bug where the 'selected' attribute can only be\n // removed if set to a non-empty value:\n // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12087679/\n fromEl.setAttribute('selected', 'selected');\n fromEl.removeAttribute('selected');\n }\n // We have to reset select element's selectedIndex to -1, otherwise setting\n // fromEl.selected using the syncBooleanAttrProp below has no effect.\n // The correct selectedIndex will be set in the SELECT special handler below.\n parentNode.selectedIndex = -1;\n }\n }\n syncBooleanAttrProp(fromEl, toEl, 'selected');\n },\n /**\n * The \"value\" attribute is special for the <input> element since it sets\n * the initial value. Changing the \"value\" attribute without changing the\n * \"value\" property will have no effect since it is only used to the set the\n * initial value. Similar for the \"checked\" attribute, and \"disabled\".\n */\n INPUT: function(fromEl, toEl) {\n syncBooleanAttrProp(fromEl, toEl, 'checked');\n syncBooleanAttrProp(fromEl, toEl, 'disabled');\n\n if (fromEl.value !== toEl.value) {\n fromEl.value = toEl.value;\n }\n\n if (!toEl.hasAttribute('value')) {\n fromEl.removeAttribute('value');\n }\n },\n\n TEXTAREA: function(fromEl, toEl) {\n var newValue = toEl.value;\n if (fromEl.value !== newValue) {\n fromEl.value = newValue;\n }\n\n var firstChild = fromEl.firstChild;\n if (firstChild) {\n // Needed for IE. Apparently IE sets the placeholder as the\n // node value and vise versa. This ignores an empty update.\n var oldValue = firstChild.nodeValue;\n\n if (oldValue == newValue || (!newValue && oldValue == fromEl.placeholder)) {\n return;\n }\n\n firstChild.nodeValue = newValue;\n }\n },\n SELECT: function(fromEl, toEl) {\n if (!toEl.hasAttribute('multiple')) {\n var selectedIndex = -1;\n var i = 0;\n // We have to loop through children of fromEl, not toEl since nodes can be moved\n // from toEl to fromEl directly when morphing.\n // At the time this special handler is invoked, all children have already been morphed\n // and appended to / removed from fromEl, so using fromEl here is safe and correct.\n var curChild = fromEl.firstChild;\n var optgroup;\n var nodeName;\n while(curChild) {\n nodeName = curChild.nodeName && curChild.nodeName.toUpperCase();\n if (nodeName === 'OPTGROUP') {\n optgroup = curChild;\n curChild = optgroup.firstChild;\n // handle empty optgroups\n if (!curChild) {\n curChild = optgroup.nextSibling;\n optgroup = null;\n }\n } else {\n if (nodeName === 'OPTION') {\n if (curChild.hasAttribute('selected')) {\n selectedIndex = i;\n break;\n }\n i++;\n }\n curChild = curChild.nextSibling;\n if (!curChild && optgroup) {\n curChild = optgroup.nextSibling;\n optgroup = null;\n }\n }\n }\n\n fromEl.selectedIndex = selectedIndex;\n }\n }\n};\n\nvar ELEMENT_NODE = 1;\nvar DOCUMENT_FRAGMENT_NODE$1 = 11;\nvar TEXT_NODE = 3;\nvar COMMENT_NODE = 8;\n\nfunction noop() {}\n\nfunction defaultGetNodeKey(node) {\n if (node) {\n return (node.getAttribute && node.getAttribute('id')) || node.id;\n }\n}\n\nfunction morphdomFactory(morphAttrs) {\n\n return function morphdom(fromNode, toNode, options) {\n if (!options) {\n options = {};\n }\n\n if (typeof toNode === 'string') {\n if (fromNode.nodeName === '#document' || fromNode.nodeName === 'HTML' || fromNode.nodeName === 'BODY') {\n var toNodeHtml = toNode;\n toNode = doc.createElement('html');\n toNode.innerHTML = toNodeHtml;\n } else {\n toNode = toElement(toNode);\n }\n } else if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE$1) {\n toNode = toNode.firstElementChild;\n }\n\n var getNodeKey = options.getNodeKey || defaultGetNodeKey;\n var onBeforeNodeAdded = options.onBeforeNodeAdded || noop;\n var onNodeAdded = options.onNodeAdded || noop;\n var onBeforeElUpdated = options.onBeforeElUpdated || noop;\n var onElUpdated = options.onElUpdated || noop;\n var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop;\n var onNodeDiscarded = options.onNodeDiscarded || noop;\n var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop;\n var skipFromChildren = options.skipFromChildren || noop;\n var addChild = options.addChild || function(parent, child){ return parent.appendChild(child); };\n var childrenOnly = options.childrenOnly === true;\n\n // This object is used as a lookup to quickly find all keyed elements in the original DOM tree.\n var fromNodesLookup = Object.create(null);\n var keyedRemovalList = [];\n\n function addKeyedRemoval(key) {\n keyedRemovalList.push(key);\n }\n\n function walkDiscardedChildNodes(node, skipKeyedNodes) {\n if (node.nodeType === ELEMENT_NODE) {\n var curChild = node.firstChild;\n while (curChild) {\n\n var key = undefined;\n\n if (skipKeyedNodes && (key = getNodeKey(curChild))) {\n // If we are skipping keyed nodes then we add the key\n // to a list so that it can be handled at the very end.\n addKeyedRemoval(key);\n } else {\n // Only report the node as discarded if it is not keyed. We do this because\n // at the end we loop through all keyed elements that were unmatched\n // and then discard them in one final pass.\n onNodeDiscarded(curChild);\n if (curChild.firstChild) {\n walkDiscardedChildNodes(curChild, skipKeyedNodes);\n }\n }\n\n curChild = curChild.nextSibling;\n }\n }\n }\n\n /**\n * Removes a DOM node out of the original DOM\n *\n * @param {Node} node The node to remove\n * @param {Node} parentNode The nodes parent\n * @param {Boolean} skipKeyedNodes If true then elements with keys will be skipped and not discarded.\n * @return {undefined}\n */\n function removeNode(node, parentNode, skipKeyedNodes) {\n if (onBeforeNodeDiscarded(node) === false) {\n return;\n }\n\n if (parentNode) {\n parentNode.removeChild(node);\n }\n\n onNodeDiscarded(node);\n walkDiscardedChildNodes(node, skipKeyedNodes);\n }\n\n // // TreeWalker implementation is no faster, but keeping this around in case this changes in the future\n // function indexTree(root) {\n // var treeWalker = document.createTreeWalker(\n // root,\n // NodeFilter.SHOW_ELEMENT);\n //\n // var el;\n // while((el = treeWalker.nextNode())) {\n // var key = getNodeKey(el);\n // if (key) {\n // fromNodesLookup[key] = el;\n // }\n // }\n // }\n\n // // NodeIterator implementation is no faster, but keeping this around in case this changes in the future\n //\n // function indexTree(node) {\n // var nodeIterator = document.createNodeIterator(node, NodeFilter.SHOW_ELEMENT);\n // var el;\n // while((el = nodeIterator.nextNode())) {\n // var key = getNodeKey(el);\n // if (key) {\n // fromNodesLookup[key] = el;\n // }\n // }\n // }\n\n function indexTree(node) {\n if (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE$1) {\n var curChild = node.firstChild;\n while (curChild) {\n var key = getNodeKey(curChild);\n if (key) {\n fromNodesLookup[key] = curChild;\n }\n\n // Walk recursively\n indexTree(curChild);\n\n curChild = curChild.nextSibling;\n }\n }\n }\n\n indexTree(fromNode);\n\n function handleNodeAdded(el) {\n onNodeAdded(el);\n\n var curChild = el.firstChild;\n while (curChild) {\n var nextSibling = curChild.nextSibling;\n\n var key = getNodeKey(curChild);\n if (key) {\n var unmatchedFromEl = fromNodesLookup[key];\n // if we find a duplicate #id node in cache, replace `el` with cache value\n // and morph it to the child node.\n if (unmatchedFromEl && compareNodeNames(curChild, unmatchedFromEl)) {\n curChild.parentNode.replaceChild(unmatchedFromEl, curChild);\n morphEl(unmatchedFromEl, curChild);\n } else {\n handleNodeAdded(curChild);\n }\n } else {\n // recursively call for curChild and it's children to see if we find something in\n // fromNodesLookup\n handleNodeAdded(curChild);\n }\n\n curChild = nextSibling;\n }\n }\n\n function cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey) {\n // We have processed all of the \"to nodes\". If curFromNodeChild is\n // non-null then we still have some from nodes left over that need\n // to be removed\n while (curFromNodeChild) {\n var fromNextSibling = curFromNodeChild.nextSibling;\n if ((curFromNodeKey = getNodeKey(curFromNodeChild))) {\n // Since the node is keyed it might be matched up later so we defer\n // the actual removal to later\n addKeyedRemoval(curFromNodeKey);\n } else {\n // NOTE: we skip nested keyed nodes from being removed since there is\n // still a chance they will be matched up later\n removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);\n }\n curFromNodeChild = fromNextSibling;\n }\n }\n\n function morphEl(fromEl, toEl, childrenOnly) {\n var toElKey = getNodeKey(toEl);\n\n if (toElKey) {\n // If an element with an ID is being morphed then it will be in the final\n // DOM so clear it out of the saved elements collection\n delete fromNodesLookup[toElKey];\n }\n\n if (!childrenOnly) {\n // optional\n var beforeUpdateResult = onBeforeElUpdated(fromEl, toEl);\n if (beforeUpdateResult === false) {\n return;\n } else if (beforeUpdateResult instanceof HTMLElement) {\n fromEl = beforeUpdateResult;\n // reindex the new fromEl in case it's not in the same\n // tree as the original fromEl\n // (Phoenix LiveView sometimes returns a cloned tree,\n // but keyed lookups would still point to the original tree)\n indexTree(fromEl);\n }\n\n // update attributes on original DOM element first\n morphAttrs(fromEl, toEl);\n // optional\n onElUpdated(fromEl);\n\n if (onBeforeElChildrenUpdated(fromEl, toEl) === false) {\n return;\n }\n }\n\n if (fromEl.nodeName !== 'TEXTAREA') {\n morphChildren(fromEl, toEl);\n } else {\n specialElHandlers.TEXTAREA(fromEl, toEl);\n }\n }\n\n function morphChildren(fromEl, toEl) {\n var skipFrom = skipFromChildren(fromEl, toEl);\n var curToNodeChild = toEl.firstChild;\n var curFromNodeChild = fromEl.firstChild;\n var curToNodeKey;\n var curFromNodeKey;\n\n var fromNextSibling;\n var toNextSibling;\n var matchingFromEl;\n\n // walk the children\n outer: while (curToNodeChild) {\n toNextSibling = curToNodeChild.nextSibling;\n curToNodeKey = getNodeKey(curToNodeChild);\n\n // walk the fromNode children all the way through\n while (!skipFrom && curFromNodeChild) {\n fromNextSibling = curFromNodeChild.nextSibling;\n\n if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) {\n curToNodeChild = toNextSibling;\n curFromNodeChild = fromNextSibling;\n continue outer;\n }\n\n curFromNodeKey = getNodeKey(curFromNodeChild);\n\n var curFromNodeType = curFromNodeChild.nodeType;\n\n // this means if the curFromNodeChild doesnt have a match with the curToNodeChild\n var isCompatible = undefined;\n\n if (curFromNodeType === curToNodeChild.nodeType) {\n if (curFromNodeType === ELEMENT_NODE) {\n // Both nodes being compared are Element nodes\n\n if (curToNodeKey) {\n // The target node has a key so we want to match it up with the correct element\n // in the original DOM tree\n if (curToNodeKey !== curFromNodeKey) {\n // The current element in the original DOM tree does not have a matching key so\n // let's check our lookup to see if there is a matching element in the original\n // DOM tree\n if ((matchingFromEl = fromNodesLookup[curToNodeKey])) {\n if (fromNextSibling === matchingFromEl) {\n // Special case for single element removals. To avoid removing the original\n // DOM node out of the tree (since that can break CSS transitions, etc.),\n // we will instead discard the current node and wait until the next\n // iteration to properly match up the keyed target element with its matching\n // element in the original tree\n isCompatible = false;\n } else {\n // We found a matching keyed element somewhere in the original DOM tree.\n // Let's move the original DOM node into the current position and morph\n // it.\n\n // NOTE: We use insertBefore instead of replaceChild because we want to go through\n // the `removeNode()` function for the node that is being discarded so that\n // all lifecycle hooks are correctly invoked\n fromEl.insertBefore(matchingFromEl, curFromNodeChild);\n\n // fromNextSibling = curFromNodeChild.nextSibling;\n\n if (curFromNodeKey) {\n // Since the node is keyed it might be matched up later so we defer\n // the actual removal to later\n addKeyedRemoval(curFromNodeKey);\n } else {\n // NOTE: we skip nested keyed nodes from being removed since there is\n // still a chance they will be matched up later\n removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);\n }\n\n curFromNodeChild = matchingFromEl;\n curFromNodeKey = getNodeKey(curFromNodeChild);\n }\n } else {\n // The nodes are not compatible since the \"to\" node has a key and there\n // is no matching keyed node in the source tree\n isCompatible = false;\n }\n }\n } else if (curFromNodeKey) {\n // The original has a key\n isCompatible = false;\n }\n\n isCompatible = isCompatible !== false && compareNodeNames(curFromNodeChild, curToNodeChild);\n if (isCompatible) {\n // We found compatible DOM elements so transform\n // the current \"from\" node to match the current\n // target DOM node.\n // MORPH\n morphEl(curFromNodeChild, curToNodeChild);\n }\n\n } else if (curFromNodeType === TEXT_NODE || curFromNodeType == COMMENT_NODE) {\n // Both nodes being compared are Text or Comment nodes\n isCompatible = true;\n // Simply update nodeValue on the original node to\n // change the text value\n if (curFromNodeChild.nodeValue !== curToNodeChild.nodeValue) {\n curFromNodeChild.nodeValue = curToNodeChild.nodeValue;\n }\n\n }\n }\n\n if (isCompatible) {\n // Advance both the \"to\" child and the \"from\" child since we found a match\n // Nothing else to do as we already recursively called morphChildren above\n curToNodeChild = toNextSibling;\n curFromNodeChild = fromNextSibling;\n continue outer;\n }\n\n // No compatible match so remove the old node from the DOM and continue trying to find a\n // match in the original DOM. However, we only do this if the from node is not keyed\n // since it is possible that a keyed node might match up with a node somewhere else in the\n // target tree and we don't want to discard it just yet since it still might find a\n // home in the final DOM tree. After everything is done we will remove any keyed nodes\n // that didn't find a home\n if (curFromNodeKey) {\n // Since the node is keyed it might be matched up later so we defer\n // the actual removal to later\n addKeyedRemoval(curFromNodeKey);\n } else {\n // NOTE: we skip nested keyed nodes from being removed since there is\n // still a chance they will be matched up later\n removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);\n }\n\n curFromNodeChild = fromNextSibling;\n } // END: while(curFromNodeChild) {}\n\n // If we got this far then we did not find a candidate match for\n // our \"to node\" and we exhausted all of the children \"from\"\n // nodes. Therefore, we will just append the current \"to\" node\n // to the end\n if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) {\n // MORPH\n if(!skipFrom){ addChild(fromEl, matchingFromEl); }\n morphEl(matchingFromEl, curToNodeChild);\n } else {\n var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild);\n if (onBeforeNodeAddedResult !== false) {\n if (onBeforeNodeAddedResult) {\n curToNodeChild = onBeforeNodeAddedResult;\n }\n\n if (curToNodeChild.actualize) {\n curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc);\n }\n addChild(fromEl, curToNodeChild);\n handleNodeAdded(curToNodeChild);\n }\n }\n\n curToNodeChild = toNextSibling;\n curFromNodeChild = fromNextSibling;\n }\n\n cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey);\n\n var specialElHandler = specialElHandlers[fromEl.nodeName];\n if (specialElHandler) {\n specialElHandler(fromEl, toEl);\n }\n } // END: morphChildren(...)\n\n var morphedNode = fromNode;\n var morphedNodeType = morphedNode.nodeType;\n var toNodeType = toNode.nodeType;\n\n if (!childrenOnly) {\n // Handle the case where we are given two DOM nodes that are not\n // compatible (e.g. <div> --> <span> or <div> --> TEXT)\n if (morphedNodeType === ELEMENT_NODE) {\n if (toNodeType === ELEMENT_NODE) {\n if (!compareNodeNames(fromNode, toNode)) {\n onNodeDiscarded(fromNode);\n morphedNode = moveChildren(fromNode, createElementNS(toNode.nodeName, toNode.namespaceURI));\n }\n } else {\n // Going from an element node to a text node\n morphedNode = toNode;\n }\n } else if (morphedNodeType === TEXT_NODE || morphedNodeType === COMMENT_NODE) { // Text or comment node\n if (toNodeType === morphedNodeType) {\n if (morphedNode.nodeValue !== toNode.nodeValue) {\n morphedNode.nodeValue = toNode.nodeValue;\n }\n\n return morphedNode;\n } else {\n // Text node to something else\n morphedNode = toNode;\n }\n }\n }\n\n if (morphedNode === toNode) {\n // The \"to node\" was not compatible with the \"from node\" so we had to\n // toss out the \"from node\" and use the \"to node\"\n onNodeDiscarded(fromNode);\n } else {\n if (toNode.isSameNode && toNode.isSameNode(morphedNode)) {\n return;\n }\n\n morphEl(morphedNode, toNode, childrenOnly);\n\n // We now need to loop over any keyed nodes that might need to be\n // removed. We only do the removal if we know that the keyed node\n // never found a match. When a keyed node is matched up we remove\n // it out of fromNodesLookup and we use fromNodesLookup to determine\n // if a keyed node has been matched up or not\n if (keyedRemovalList) {\n for (var i=0, len=keyedRemovalList.length; i<len; i++) {\n var elToRemove = fromNodesLookup[keyedRemovalList[i]];\n if (elToRemove) {\n removeNode(elToRemove, elToRemove.parentNode, false);\n }\n }\n }\n }\n\n if (!childrenOnly && morphedNode !== fromNode && fromNode.parentNode) {\n if (morphedNode.actualize) {\n morphedNode = morphedNode.actualize(fromNode.ownerDocument || doc);\n }\n // If we had to swap out the from node with a new node because the old\n // node was not compatible with the target node then we need to\n // replace the old DOM node in the original DOM tree. This is only\n // possible if the original DOM node was part of a DOM tree which\n // we know is the case if it has a parent node.\n fromNode.parentNode.replaceChild(morphedNode, fromNode);\n }\n\n return morphedNode;\n };\n}\n\nvar morphdom = morphdomFactory(morphAttrs);\n\nexport default morphdom;\n", "export const DEFAULT_CHANGE_DEBOUNCE_MS = 300;\n\nexport const FOCUSABLE_INPUTS = [\n \"text\",\n \"textarea\",\n \"number\",\n \"email\",\n \"password\",\n \"search\",\n \"tel\",\n \"url\",\n \"date\",\n \"time\",\n \"datetime-local\",\n \"color\",\n \"range\",\n];\n", "import { FOCUSABLE_INPUTS } from \"../constants\";\nimport type { Logger } from \"../utils/logger\";\n\nexport class FocusManager {\n private wrapperElement: Element | null = null;\n private focusableElements: HTMLElement[] = [];\n private lastFocusedElement: HTMLElement | null = null;\n private lastFocusedSelectionStart: number | null = null;\n private lastFocusedSelectionEnd: number | null = null;\n\n constructor(private readonly logger: Logger) {}\n\n attach(wrapper: Element | null): void {\n this.wrapperElement = wrapper;\n\n if (!wrapper) {\n return;\n }\n\n this.updateFocusableElements();\n this.setupFocusTracking();\n }\n\n reset(): void {\n this.wrapperElement = null;\n this.focusableElements = [];\n this.lastFocusedElement = null;\n this.lastFocusedSelectionStart = null;\n this.lastFocusedSelectionEnd = null;\n }\n\n updateFocusableElements(): void {\n if (!this.wrapperElement) return;\n\n const inputSelectors = FOCUSABLE_INPUTS.map((type) =>\n type === \"textarea\"\n ? \"textarea:not([disabled])\"\n : `input[type=\"${type}\"]:not([disabled])`\n ).join(\", \");\n\n const otherFocusable =\n 'select:not([disabled]), button:not([disabled]), [contenteditable=\"true\"], [tabindex]:not([tabindex=\"-1\"])';\n const selector = `${inputSelectors}, ${otherFocusable}`;\n\n this.focusableElements = Array.from(\n this.wrapperElement.querySelectorAll(selector)\n );\n }\n\n setupFocusTracking(): void {\n if (!this.wrapperElement) return;\n\n const wrapperId = this.wrapperElement.getAttribute(\"data-lvt-id\");\n const focusKey = `__lvt_focus_tracker_${wrapperId}`;\n const blurKey = `__lvt_blur_tracker_${wrapperId}`;\n\n const focusListener = (event: Event) => {\n const target = event.target as HTMLElement;\n if (!target || !this.wrapperElement?.contains(target)) return;\n\n if (this.isTextualInput(target) || target instanceof HTMLSelectElement) {\n this.lastFocusedElement = target;\n this.logger.debug(\n \"[Focus] Tracked focus on:\",\n target.tagName,\n target.id || target.getAttribute(\"name\")\n );\n\n if (this.isTextualInput(target)) {\n this.lastFocusedSelectionStart = target.selectionStart;\n this.lastFocusedSelectionEnd = target.selectionEnd;\n }\n }\n };\n\n const blurListener = (event: Event) => {\n const target = event.target as HTMLElement;\n if (!target || !this.wrapperElement?.contains(target)) return;\n\n if (this.isTextualInput(target) && target === this.lastFocusedElement) {\n this.lastFocusedSelectionStart = target.selectionStart;\n this.lastFocusedSelectionEnd = target.selectionEnd;\n this.logger.debug(\n \"[Focus] Saved cursor on blur:\",\n this.lastFocusedSelectionStart,\n \"-\",\n this.lastFocusedSelectionEnd\n );\n }\n };\n\n if ((document as any)[focusKey]) {\n document.removeEventListener(\"focus\", (document as any)[focusKey], true);\n }\n if ((document as any)[blurKey]) {\n document.removeEventListener(\"blur\", (document as any)[blurKey], true);\n }\n\n (document as any)[focusKey] = focusListener;\n (document as any)[blurKey] = blurListener;\n\n document.addEventListener(\"focus\", focusListener, true);\n document.addEventListener(\"blur\", blurListener, true);\n\n this.logger.debug(\"[Focus] Focus tracking set up\");\n }\n\n restoreFocusedElement(): void {\n this.logger.debug(\n \"[Focus] restoreFocusedElement - lastFocusedElement:\",\n this.lastFocusedElement?.tagName,\n this.lastFocusedElement?.id ||\n this.lastFocusedElement?.getAttribute(\"name\")\n );\n\n if (!this.lastFocusedElement || !this.wrapperElement) {\n this.logger.debug(\"[Focus] No element to restore\");\n return;\n }\n\n const selector = this.getElementSelector(this.lastFocusedElement);\n this.logger.debug(\"[Focus] Selector for last focused:\", selector);\n\n if (!selector) {\n this.logger.debug(\"[Focus] Could not generate selector\");\n return;\n }\n\n let element: HTMLElement | null = null;\n\n if (selector.startsWith(\"data-focus-index-\")) {\n this.updateFocusableElements();\n const index = parseInt(selector.replace(\"data-focus-index-\", \"\"), 10);\n element = this.focusableElements[index] || null;\n this.logger.debug(\"[Focus] Found by index:\", index, element?.tagName);\n } else {\n element = this.wrapperElement.querySelector(selector);\n this.logger.debug(\n \"[Focus] Found by selector:\",\n selector,\n element?.tagName\n );\n }\n\n if (!element) {\n this.logger.debug(\"[Focus] Element not found in updated DOM\");\n return;\n }\n\n const wasFocused = element.matches(\":focus\");\n this.logger.debug(\"[Focus] Already focused:\", wasFocused);\n\n if (wasFocused) {\n this.logger.debug(\n \"[Focus] Element retained focus \u2014 skipping restoration\"\n );\n return;\n }\n\n // Capture selection before focus() \u2014 the focus event listener fires\n // synchronously and overwrites lastFocusedSelection* with the new\n // element's current (wrong) cursor position.\n const selectionStart = this.lastFocusedSelectionStart;\n const selectionEnd = this.lastFocusedSelectionEnd;\n\n element.focus();\n this.logger.debug(\"[Focus] Restored focus\");\n\n if (\n this.isTextualInput(element) &&\n selectionStart !== null &&\n selectionEnd !== null\n ) {\n element.setSelectionRange(selectionStart, selectionEnd);\n this.logger.debug(\n \"[Focus] Restored cursor:\",\n selectionStart,\n \"-\",\n selectionEnd\n );\n }\n }\n\n isTextualInput(el: Element): el is HTMLInputElement | HTMLTextAreaElement {\n if (el instanceof HTMLTextAreaElement) return true;\n if (el instanceof HTMLInputElement) {\n return FOCUSABLE_INPUTS.indexOf(el.type) >= 0;\n }\n return false;\n }\n\n shouldSkipUpdate(el: Element): boolean {\n if (el !== document.activeElement) {\n return false;\n }\n\n if (el.hasAttribute(\"data-lvt-force-update\")) {\n return false;\n }\n\n if (this.isTextualInput(el)) {\n return true;\n }\n\n if (el instanceof HTMLSelectElement) {\n return true;\n }\n\n return false;\n }\n\n getLastFocusedElement(): HTMLElement | null {\n return this.lastFocusedElement;\n }\n\n private getElementSelector(el: HTMLElement): string | null {\n if (el.id) return `#${el.id}`;\n if ((el as any).name) return `[name=\"${(el as any).name}\"]`;\n if (el.getAttribute(\"data-key\"))\n return `[data-key=\"${el.getAttribute(\"data-key\")}\"]`;\n\n const index = this.focusableElements.indexOf(el);\n return index >= 0 ? `data-focus-index-${index}` : null;\n }\n}\n", "/**\n * Reactive Attributes - Declarative DOM actions triggered by lifecycle events or interactions.\n *\n * Attribute Pattern: lvt-el:{method}:on:{trigger}=\"param\"\n *\n * Trigger types:\n *\n * 1. Lifecycle states (server action request-response cycle):\n * - pending, success, error, done\n * - Supports action scoping: lvt-el:reset:on:create-todo:success\n *\n * 2. Native DOM events (client-side, no server round-trip):\n * - Any browser event: click, focusin, focusout, mouseenter, mouseleave, keydown, etc.\n * - No action scoping (fires on the element's own event)\n *\n * 3. Synthetic interactions (client-side):\n * - click-away: Click outside the element\n * - No action scoping\n *\n * Methods:\n * - reset: Calls form.reset()\n * - addClass: Adds CSS class(es)\n * - removeClass: Removes CSS class(es)\n * - toggleClass: Toggles CSS class(es)\n * - setAttr: Sets an attribute (name:value format)\n * - toggleAttr: Toggles a boolean attribute\n */\n\nexport type ReactiveAction =\n | \"reset\"\n | \"addClass\"\n | \"removeClass\"\n | \"toggleClass\"\n | \"setAttr\"\n | \"toggleAttr\";\n\nexport type LifecycleEvent = \"pending\" | \"success\" | \"error\" | \"done\";\n\nexport interface ReactiveBinding {\n action: ReactiveAction;\n lifecycle: LifecycleEvent;\n actionName?: string;\n param?: string;\n}\n\nconst LIFECYCLE_EVENTS: LifecycleEvent[] = [\"pending\", \"success\", \"error\", \"done\"];\nconst LIFECYCLE_SET = new Set<string>(LIFECYCLE_EVENTS);\n\n/**\n * Reserved trigger keywords that are NOT native DOM events.\n * click-away is a synthetic interaction handled by setupClickAwayDelegation.\n * Everything else that's not a lifecycle state is treated as a native DOM event.\n */\nexport const SYNTHETIC_TRIGGERS = new Set([\"click-away\"]);\n\n// Lowercase method names \u2192 canonical ReactiveAction\nconst METHOD_MAP: Record<string, ReactiveAction> = {\n reset: \"reset\",\n addclass: \"addClass\",\n removeclass: \"removeClass\",\n toggleclass: \"toggleClass\",\n setattr: \"setAttr\",\n toggleattr: \"toggleAttr\",\n};\n\n/**\n * Parse a reactive attribute name and value into a binding.\n *\n * Supported pattern: lvt-el:{method}:on:[{action}:]{state}\n *\n * Examples:\n * parseReactiveAttribute(\"lvt-el:reset:on:success\", \"\") => { action: \"reset\", lifecycle: \"success\" }\n * parseReactiveAttribute(\"lvt-el:addclass:on:pending\", \"loading\") => { action: \"addClass\", lifecycle: \"pending\", param: \"loading\" }\n * parseReactiveAttribute(\"lvt-el:reset:on:create-todo:success\", \"\") => { action: \"reset\", lifecycle: \"success\", actionName: \"create-todo\" }\n */\nexport function parseReactiveAttribute(\n attrName: string,\n attrValue: string\n): ReactiveBinding | null {\n const lower = attrName.toLowerCase();\n\n // New pattern: lvt-el:{method}:on:[{action}:]{state}\n const newMatch = lower.match(/^lvt-el:(\\w+):on:(.+)$/);\n if (newMatch) {\n const methodKey = newMatch[1];\n const action = METHOD_MAP[methodKey];\n if (!action) return null;\n\n const eventPart = newMatch[2];\n // Skip synthetic triggers (click-away) \u2014 handled by setupClickAwayDelegation\n if (SYNTHETIC_TRIGGERS.has(eventPart)) return null;\n // Skip native DOM event triggers \u2014 handled by setupDOMEventTriggerDelegation\n if (!LIFECYCLE_SET.has(eventPart) && !eventPart.includes(\":\")) return null;\n\n const segments = eventPart.split(\":\");\n const lastSegment = segments[segments.length - 1];\n if (!LIFECYCLE_SET.has(lastSegment)) return null;\n\n const lifecycle = lastSegment as LifecycleEvent;\n const actionName = segments.length > 1 ? segments.slice(0, -1).join(\":\") : undefined;\n\n return {\n action,\n lifecycle,\n actionName: actionName || undefined,\n param: attrValue || undefined,\n };\n }\n\n return null;\n}\n\n/**\n * Resolve the target element for an lvt-el: action.\n * If the element has data-lvt-target, resolves to the specified element:\n * - \"#id\" \u2192 document.getElementById(id)\n * - \"closest:sel\" \u2192 element.closest(sel)\n * Falls back to the element itself if no target or target not found.\n */\nexport function resolveTarget(element: Element): Element {\n const selector = element.getAttribute(\"data-lvt-target\");\n if (!selector) return element;\n if (selector.startsWith(\"#\")) {\n return document.getElementById(selector.slice(1)) || element;\n }\n if (selector.startsWith(\"closest:\")) {\n return element.closest(selector.slice(8)) || element;\n }\n return element;\n}\n\n/**\n * Execute a reactive action on an element.\n */\nexport function executeAction(\n element: Element,\n action: ReactiveAction,\n param?: string\n): void {\n switch (action) {\n case \"reset\":\n if (element instanceof HTMLFormElement) {\n element.reset();\n }\n break;\n\n case \"addClass\":\n if (param) {\n const classes = param.split(/\\s+/).filter(Boolean);\n element.classList.add(...classes);\n }\n break;\n\n case \"removeClass\":\n if (param) {\n const classes = param.split(/\\s+/).filter(Boolean);\n element.classList.remove(...classes);\n }\n break;\n\n case \"toggleClass\":\n if (param) {\n const classes = param.split(/\\s+/).filter(Boolean);\n classes.forEach((c) => element.classList.toggle(c));\n }\n break;\n\n case \"setAttr\":\n if (param) {\n const colonIndex = param.indexOf(\":\");\n if (colonIndex > 0) {\n const name = param.substring(0, colonIndex);\n const value = param.substring(colonIndex + 1);\n element.setAttribute(name, value);\n }\n }\n break;\n\n case \"toggleAttr\":\n if (param) {\n element.toggleAttribute(param);\n }\n break;\n }\n}\n\n/**\n * Check if an event matches a binding.\n */\nexport function matchesEvent(\n binding: ReactiveBinding,\n lifecycle: LifecycleEvent,\n actionName?: string\n): boolean {\n if (binding.lifecycle !== lifecycle) return false;\n if (!binding.actionName) return true;\n return binding.actionName === actionName;\n}\n\n/**\n * Process all reactive attributes for a lifecycle event.\n */\nexport function processReactiveAttributes(\n lifecycle: LifecycleEvent,\n actionName?: string\n): void {\n // Target only elements with lvt-el: attributes instead of scanning all DOM elements.\n // CSS doesn't support attribute-name-starts-with, so we build selectors from known\n // method prefixes. This covers both unscoped (lvt-el:reset:on:success) and\n // action-scoped (lvt-el:reset:on:create-todo:success) patterns.\n const methodKeys = Object.keys(METHOD_MAP);\n const selectorParts: string[] = [];\n\n // Escape CSS-special characters in actionName for use in attribute selectors\n const escapedAction = actionName\n ? actionName.replace(/([^\\w-])/g, \"\\\\$1\")\n : undefined;\n\n for (const m of methodKeys) {\n selectorParts.push(`[lvt-el\\\\:${m}\\\\:on\\\\:${lifecycle}]`);\n if (escapedAction) {\n selectorParts.push(`[lvt-el\\\\:${m}\\\\:on\\\\:${escapedAction}\\\\:${lifecycle}]`);\n }\n }\n const selector = selectorParts.join(\", \");\n\n let candidates: NodeListOf<Element>;\n try {\n candidates = document.querySelectorAll(selector);\n } catch {\n // If selector is still invalid despite escaping, scan targeted elements only\n // by matching unscoped patterns (without actionName)\n const fallbackParts = methodKeys.map(m => `[lvt-el\\\\:${m}\\\\:on\\\\:${lifecycle}]`);\n try {\n candidates = document.querySelectorAll(fallbackParts.join(\", \"));\n } catch {\n return; // Cannot construct any valid selector\n }\n }\n\n candidates.forEach((element) => {\n Array.from(element.attributes).forEach((attr) => {\n if (!attr.name.startsWith(\"lvt-el:\") || !attr.name.includes(\":on:\")) {\n return;\n }\n\n const binding = parseReactiveAttribute(attr.name, attr.value);\n if (binding && matchesEvent(binding, lifecycle, actionName)) {\n executeAction(resolveTarget(element), binding.action, binding.param);\n }\n });\n });\n}\n\n/**\n * Process all lvt-el:*:on:{trigger} attributes on an element for a given trigger.\n */\nexport function processElementInteraction(element: Element, trigger: string): void {\n for (const attr of element.attributes) {\n const match = attr.name.match(/^lvt-el:(\\w+):on:([a-z-]+)$/i);\n if (!match) continue;\n if (match[2].toLowerCase() !== trigger) continue;\n\n const methodKey = match[1].toLowerCase();\n const action = METHOD_MAP[methodKey];\n if (!action) continue;\n\n executeAction(resolveTarget(element), action, attr.value);\n }\n}\n\n/**\n * Checks if a trigger name is a DOM event (not lifecycle or synthetic).\n * Intentionally open \u2014 accepts any string to support both native DOM events\n * and custom events (e.g., lvt-el:addClass:on:my-custom-event). A typo\n * silently registers a listener that never fires; no allowlist is enforced.\n */\nexport function isDOMEventTrigger(trigger: string): boolean {\n return !LIFECYCLE_SET.has(trigger) && !SYNTHETIC_TRIGGERS.has(trigger);\n}\n\n/**\n * Set up document-level event listeners for reactive attributes.\n */\nexport function setupReactiveAttributeListeners(): void {\n LIFECYCLE_EVENTS.forEach((lifecycle) => {\n document.addEventListener(\n `lvt:${lifecycle}`,\n (e: Event) => {\n const customEvent = e as CustomEvent;\n const actionName = customEvent.detail?.action;\n processReactiveAttributes(lifecycle, actionName);\n },\n true\n );\n });\n}\n", "import { isDOMEventTrigger, SYNTHETIC_TRIGGERS } from \"./reactive-attributes\";\n\n// \u2500\u2500\u2500 Trigger parsing for lvt-fx: attributes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst FX_LIFECYCLE_SET = new Set([\"pending\", \"success\", \"error\", \"done\"]);\n\n// Tracks elements whose entry animation has already played. Kept as a\n// module-level WeakSet (rather than stashed on the DOM node) so it's\n// type-safe and automatically cleaned up when elements are GC'd.\n//\n// Semantic: once per element lifetime. An element added to this set will\n// NEVER animate again, even if the same node is updated in place. This is\n// intentional \u2014 lvt-fx:animate is an entry animation, not a per-update\n// flash. Morphdom creates fresh DOM nodes for newly-inserted range items\n// (which are not in the set, so they animate) while reusing nodes for\n// in-place updates (already in the set, so they skip). Use cases that\n// want a visible pulse on every update should reach for lvt-fx:highlight.\nlet animatedElements = new WeakSet<Element>();\n\n/**\n * Test-only: reset the module-level animatedElements WeakSet. Required\n * for tests that reuse the same DOM nodes across cases \u2014 without this,\n * an element animated in case 1 would be silently skipped in case 2.\n * Production code should never call this.\n *\n * The double-underscore prefix and the `@internal` tag signal that this\n * is not part of the public API. The `@internal` tag is only enforced\n * when TypeScript's API Extractor (or equivalent) is configured to\n * strip it from generated `.d.ts` files \u2014 this project does not\n * currently run API Extractor, so the tag is aspirational enforcement\n * backed by the `__` naming convention and this docstring.\n *\n * @internal\n */\nexport function __resetAnimatedElementsForTesting(): void {\n animatedElements = new WeakSet<Element>();\n}\n\n/**\n * Parse a lvt-fx:{effect}[:on:[{action}:]{trigger}] attribute name.\n * Returns the trigger type or null for implicit (no :on:).\n */\nfunction parseFxTrigger(attrName: string): { trigger: string | null; actionName?: string } {\n // Check for :on: suffix pattern\n const onMatch = attrName.match(/^lvt-fx:\\w+:on:(.+)$/i);\n if (!onMatch) return { trigger: null }; // implicit trigger\n\n const parts = onMatch[1].split(\":\");\n if (parts.length === 1) {\n return { trigger: parts[0].toLowerCase() };\n }\n // action-scoped: lvt-fx:highlight:on:save:success\n return {\n trigger: parts[parts.length - 1].toLowerCase(),\n actionName: parts.slice(0, -1).join(\":\"),\n };\n}\n\n/**\n * Set up DOM event listeners for lvt-fx: attributes with :on:{event} triggers.\n * Called after each DOM update to handle new elements.\n *\n * @param scanRoot - Element subtree to scan for new fx attributes.\n * @param registryRoot - Element to store listener registry on (always the wrapper).\n * Defaults to scanRoot for backwards compatibility.\n */\nexport function setupFxDOMEventTriggers(scanRoot: Element, registryRoot?: Element): void {\n const registry = registryRoot || scanRoot;\n const fxListenersKey = \"__lvtFxDirectListeners\";\n // Prune stale entries from elements replaced by morphdom\n const fxListeners: Array<{ el: Element; event: string; handler: EventListener; guardKey: string }> =\n ((registry as any)[fxListenersKey] || []).filter(\n (entry: { el: Element }) => entry.el.isConnected\n );\n\n const processEl = (el: Element) => {\n for (const attr of el.attributes) {\n if (!attr.name.startsWith(\"lvt-fx:\")) continue;\n const parsed = parseFxTrigger(attr.name);\n if (!parsed.trigger) continue; // implicit \u2014 handled by normal directive flow\n if (FX_LIFECYCLE_SET.has(parsed.trigger)) continue; // lifecycle \u2014 handled by event listeners\n if (SYNTHETIC_TRIGGERS.has(parsed.trigger)) continue; // click-away etc.\n\n // It's a DOM event trigger\n const listenerKey = `__lvt_fx_${attr.name}`;\n if ((el as any)[listenerKey]) continue; // already attached\n\n const effect = attr.name.match(/^lvt-fx:(\\w+)/i)?.[1];\n if (!effect) continue;\n\n const attrNameCapture = attr.name;\n const listener = () => {\n if (!el.hasAttribute(attrNameCapture)) return; // attr removed by morphdom\n const currentValue = el.getAttribute(attrNameCapture) || \"\";\n applyFxEffect(el as HTMLElement, effect, currentValue);\n };\n el.addEventListener(parsed.trigger, listener);\n (el as any)[listenerKey] = listener;\n fxListeners.push({ el, event: parsed.trigger, handler: listener, guardKey: listenerKey });\n }\n };\n\n // Process scan root element itself then descendants (avoids spreading NodeList)\n processEl(scanRoot);\n scanRoot.querySelectorAll(\"*\").forEach(processEl);\n\n (registry as any)[fxListenersKey] = fxListeners;\n}\n\n/**\n * Remove direct DOM event listeners registered by setupFxDOMEventTriggers.\n * Call on disconnect to prevent stale listeners across reconnects.\n */\nexport function teardownFxDOMEventTriggers(rootElement: Element): void {\n const fxListenersKey = \"__lvtFxDirectListeners\";\n const listeners: Array<{ el: Element; event: string; handler: EventListener; guardKey: string }> | undefined =\n (rootElement as any)[fxListenersKey];\n if (listeners) {\n listeners.forEach(({ el, event, handler, guardKey }) => {\n el.removeEventListener(event, handler);\n delete (el as any)[guardKey]; // Clear per-element marker so re-attach works on reconnect\n });\n delete (rootElement as any)[fxListenersKey];\n }\n}\n\n/**\n * Process lvt-fx: attributes triggered by a lifecycle event.\n */\nexport function processFxLifecycleAttributes(\n rootElement: Element,\n lifecycle: string,\n actionName?: string,\n): void {\n const processEl = (el: Element) => {\n for (const attr of el.attributes) {\n if (!attr.name.startsWith(\"lvt-fx:\")) continue;\n const parsed = parseFxTrigger(attr.name);\n if (!parsed.trigger || !FX_LIFECYCLE_SET.has(parsed.trigger)) continue;\n if (parsed.trigger !== lifecycle) continue;\n if (parsed.actionName && parsed.actionName !== actionName) continue;\n\n const effect = attr.name.match(/^lvt-fx:(\\w+)/i)?.[1];\n if (!effect) continue;\n\n applyFxEffect(el as HTMLElement, effect, attr.value);\n }\n };\n processEl(rootElement);\n rootElement.querySelectorAll(\"*\").forEach(processEl);\n}\n\n/**\n * Apply a visual effect to an element.\n */\nfunction applyFxEffect(htmlElement: HTMLElement, effect: string, config: string): void {\n const computed = getComputedStyle(htmlElement);\n\n switch (effect) {\n case \"highlight\": {\n // Skip if already mid-highlight to prevent stale originalBackground capture.\n // Intentionally rate-limits to one highlight per element \u2014 overlapping triggers\n // (rapid clicks, DOM updates during animation) are coalesced rather than stacked.\n if ((htmlElement as any).__lvtHighlighting) break;\n (htmlElement as any).__lvtHighlighting = true;\n\n const duration = parseInt(\n computed.getPropertyValue(\"--lvt-highlight-duration\").trim() || \"500\", 10\n );\n const color = computed.getPropertyValue(\"--lvt-highlight-color\").trim() || \"#ffc107\";\n const originalBackground = htmlElement.style.backgroundColor;\n const originalTransition = htmlElement.style.transition;\n\n htmlElement.style.transition = `background-color ${duration}ms ease-out`;\n htmlElement.style.backgroundColor = color;\n\n setTimeout(() => {\n if (!htmlElement.isConnected) {\n htmlElement.style.backgroundColor = originalBackground;\n htmlElement.style.transition = originalTransition;\n (htmlElement as any).__lvtHighlighting = false;\n return;\n }\n htmlElement.style.backgroundColor = originalBackground;\n setTimeout(() => {\n if (htmlElement.isConnected) htmlElement.style.transition = originalTransition;\n (htmlElement as any).__lvtHighlighting = false;\n }, duration);\n }, 50);\n break;\n }\n case \"animate\": {\n // \"Entry animation\" semantics: play once per element lifetime. Every\n // tree update re-walks lvt-fx:* attributes, so without this guard an\n // unchanged row re-fires the animation on every patch. Morphdom\n // creates fresh DOM nodes for new rows (not in the WeakSet \u2192 animate);\n // reused nodes are already in the set and skip.\n if (animatedElements.has(htmlElement)) break;\n animatedElements.add(htmlElement);\n\n const duration = parseInt(\n computed.getPropertyValue(\"--lvt-animate-duration\").trim() || \"500\", 10\n );\n const animation = config || \"fade\";\n\n let animationValue = \"\";\n switch (animation) {\n case \"fade\":\n animationValue = `lvt-fade-in ${duration}ms ease-out`;\n break;\n case \"slide\":\n animationValue = `lvt-slide-in ${duration}ms ease-out`;\n break;\n case \"scale\":\n animationValue = `lvt-scale-in ${duration}ms ease-out`;\n break;\n default:\n console.warn(`Unknown lvt-fx:animate mode: ${animation}`);\n }\n if (!animationValue) break;\n htmlElement.style.animation = animationValue;\n htmlElement.addEventListener(\"animationend\", () => {\n // Only remove the animation we set. Do NOT remove\n // --lvt-animate-duration: users may have set it inline themselves\n // (e.g. style=\"--lvt-animate-duration: 800\") to override duration,\n // and removing would wipe their intent. Clean up the style\n // attribute entirely only if nothing is left on it.\n htmlElement.style.removeProperty(\"animation\");\n if (htmlElement.style.length === 0) {\n htmlElement.removeAttribute(\"style\");\n }\n }, { once: true });\n break;\n }\n case \"scroll\": {\n const rawBehavior = computed.getPropertyValue(\"--lvt-scroll-behavior\").trim();\n const behavior: ScrollBehavior = VALID_SCROLL_BEHAVIORS.has(rawBehavior)\n ? (rawBehavior as ScrollBehavior) : \"auto\";\n const threshold = parseInt(\n computed.getPropertyValue(\"--lvt-scroll-threshold\").trim() || \"100\", 10\n );\n const mode = config || \"bottom\";\n\n switch (mode) {\n case \"bottom\":\n htmlElement.scrollTo({ top: htmlElement.scrollHeight, behavior });\n break;\n case \"bottom-sticky\": {\n const isNearBottom = htmlElement.scrollHeight - htmlElement.scrollTop - htmlElement.clientHeight <= threshold;\n if (isNearBottom) htmlElement.scrollTo({ top: htmlElement.scrollHeight, behavior });\n break;\n }\n case \"top\":\n htmlElement.scrollTo({ top: 0, behavior });\n break;\n case \"preserve\":\n break;\n default:\n console.warn(`Unknown lvt-fx:scroll mode: ${mode}`);\n }\n break;\n }\n default:\n console.warn(`Unknown lvt-fx effect: ${effect}`);\n }\n}\n\n/**\n * Set up document-level lifecycle listeners for lvt-fx: attributes with :on:{lifecycle}.\n * Called once per wrapper at connect time. Scoped to the provided root element so\n * multiple LiveTemplateClient instances on the same page don't cross-fire effects.\n * Stores listener references on the element for teardown via teardownFxLifecycleListeners.\n */\nexport function setupFxLifecycleListeners(rootElement: Element): void {\n const guardKey = \"__lvtFxLifecycleSetup\";\n if ((rootElement as any)[guardKey]) return;\n (rootElement as any)[guardKey] = true;\n\n const listeners: Array<{ event: string; handler: EventListener }> = [];\n const lifecycles = [\"pending\", \"success\", \"error\", \"done\"];\n lifecycles.forEach(lifecycle => {\n const handler = (e: Event) => {\n const customEvent = e as CustomEvent;\n const actionName = customEvent.detail?.action;\n processFxLifecycleAttributes(rootElement, lifecycle, actionName);\n };\n document.addEventListener(`lvt:${lifecycle}`, handler, true);\n listeners.push({ event: `lvt:${lifecycle}`, handler });\n });\n (rootElement as any).__lvtFxLifecycleListeners = listeners;\n}\n\n/**\n * Remove document-level lifecycle listeners registered by setupFxLifecycleListeners.\n * Call on disconnect to prevent listener accumulation across reconnects.\n */\nexport function teardownFxLifecycleListeners(rootElement: Element): void {\n const listeners: Array<{ event: string; handler: EventListener }> | undefined =\n (rootElement as any).__lvtFxLifecycleListeners;\n if (listeners) {\n listeners.forEach(({ event, handler }) => {\n document.removeEventListener(event, handler, true);\n });\n delete (rootElement as any).__lvtFxLifecycleListeners;\n }\n delete (rootElement as any).__lvtFxLifecycleSetup;\n}\n\n// \u2500\u2500\u2500 Implicit-trigger directive handlers (fire on every DOM update) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Apply scroll directives on elements with lvt-fx:scroll attributes.\n * Only processes attributes WITHOUT :on: suffix (implicit trigger).\n * Configuration read from CSS custom properties:\n * --lvt-scroll-behavior: auto | smooth (default: auto)\n * --lvt-scroll-threshold: <number> (default: 100)\n */\nconst VALID_SCROLL_BEHAVIORS = new Set([\"auto\", \"smooth\", \"instant\"]);\n\nexport function handleScrollDirectives(rootElement: Element): void {\n rootElement.querySelectorAll(\"[lvt-fx\\\\:scroll]\").forEach((element) => {\n const mode = element.getAttribute(\"lvt-fx:scroll\");\n if (!mode) return;\n applyFxEffect(element as HTMLElement, \"scroll\", mode);\n });\n}\n\n/**\n * Apply highlight directives to elements with lvt-fx:highlight attributes.\n * Configuration read from CSS custom properties:\n * --lvt-highlight-duration: <ms> (default: 500)\n * --lvt-highlight-color: <color> (default: #ffc107)\n */\nexport function handleHighlightDirectives(rootElement: Element): void {\n rootElement.querySelectorAll(\"[lvt-fx\\\\:highlight]\").forEach((element) => {\n const mode = element.getAttribute(\"lvt-fx:highlight\");\n if (!mode) return;\n applyFxEffect(element as HTMLElement, \"highlight\", mode);\n });\n}\n\n/**\n * Apply animation directives to elements with lvt-fx:animate attributes.\n * Configuration read from CSS custom properties:\n * --lvt-animate-duration: <ms> (default: 300)\n */\nexport function handleAnimateDirectives(rootElement: Element): void {\n rootElement.querySelectorAll(\"[lvt-fx\\\\:animate]\").forEach((element) => {\n const animation = element.getAttribute(\"lvt-fx:animate\");\n if (!animation) return;\n applyFxEffect(element as HTMLElement, \"animate\", animation);\n });\n\n ensureAnimateKeyframes();\n}\n\nfunction ensureAnimateKeyframes(): void {\n if (!document.getElementById(\"lvt-animate-styles\")) {\n const style = document.createElement(\"style\");\n style.id = \"lvt-animate-styles\";\n style.textContent = `\n @keyframes lvt-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n @keyframes lvt-slide-in {\n from { opacity: 0; transform: translateY(-10px); }\n to { opacity: 1; transform: translateY(0); }\n }\n @keyframes lvt-scale-in {\n from { opacity: 0; transform: scale(0.95); }\n to { opacity: 1; transform: scale(1); }\n }\n `;\n document.head.appendChild(style);\n }\n}\n\n// \u2500\u2500\u2500 Toast directives \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface ToastMessage {\n id: string;\n title?: string;\n body?: string;\n type: \"info\" | \"success\" | \"warning\" | \"error\";\n dismissible: boolean;\n dismissMS: number;\n}\n\n// Key used to store the last processed data-pending value on each trigger element.\n// Prevents showing the same batch of toasts twice if handleToastDirectives is\n// called multiple times within a single update cycle (e.g. from multiple patches).\nconst PENDING_PROCESSED_KEY = \"__lvtPendingProcessed\";\n\n/**\n * Read data-pending toast messages from server trigger elements and create\n * client-managed toast DOM. Called after each LiveTemplate DOM update.\n */\nexport function handleToastDirectives(rootElement: Element): void {\n rootElement\n .querySelectorAll<HTMLElement>(\"[data-toast-trigger]\")\n .forEach((trigger) => {\n const pending = trigger.getAttribute(\"data-pending\");\n if (!pending) return;\n // Skip if this exact batch was already processed (handles multi-patch calls)\n if ((trigger as any)[PENDING_PROCESSED_KEY] === pending) return;\n (trigger as any)[PENDING_PROCESSED_KEY] = pending;\n\n let messages: ToastMessage[];\n try {\n messages = JSON.parse(pending);\n } catch {\n return;\n }\n if (!Array.isArray(messages) || !messages.length) return;\n\n const position = trigger.getAttribute(\"data-position\") || \"top-right\";\n const stack = getOrCreateToastStack(position);\n messages.forEach((msg) => {\n const el = createToastElement(msg);\n stack.appendChild(el);\n if (typeof msg.dismissMS === \"number\" && msg.dismissMS > 0) {\n setTimeout(() => el.remove(), msg.dismissMS);\n }\n });\n });\n}\n\n/**\n * Set up a document click listener that dismisses all visible toasts when\n * the user clicks outside the toast stack. Called once at connect time.\n */\nexport function setupToastClickOutside(): void {\n const key = \"__lvt_toast_click_outside\";\n const existing = (document as any)[key];\n if (existing) document.removeEventListener(\"click\", existing);\n const listener = (e: Event) => {\n const stack = document.querySelector(\"[data-lvt-toast-stack]\");\n if (!stack || stack.contains(e.target as Node)) return;\n stack.querySelectorAll(\"[data-lvt-toast-item]\").forEach((el) => el.remove());\n };\n (document as any)[key] = listener;\n document.addEventListener(\"click\", listener);\n}\n\nfunction getOrCreateToastStack(position: string): HTMLElement {\n let stack = document.querySelector(\n \"[data-lvt-toast-stack]\"\n ) as HTMLElement | null;\n if (!stack) {\n stack = document.createElement(\"div\");\n stack.setAttribute(\"data-lvt-toast-stack\", \"\");\n stack.setAttribute(\"aria-live\", \"polite\");\n applyPositionStyles(stack, position);\n document.body.appendChild(stack);\n }\n return stack;\n}\n\nfunction applyPositionStyles(stack: HTMLElement, position: string): void {\n const s = stack.style;\n switch (position) {\n case \"top-left\":\n s.top = \"1rem\"; s.left = \"1rem\"; break;\n case \"top-center\":\n s.top = \"1rem\"; s.left = \"50%\"; s.transform = \"translateX(-50%)\"; break;\n case \"bottom-right\":\n s.bottom = \"1rem\"; s.right = \"1rem\"; break;\n case \"bottom-left\":\n s.bottom = \"1rem\"; s.left = \"1rem\"; break;\n case \"bottom-center\":\n s.bottom = \"1rem\"; s.left = \"50%\"; s.transform = \"translateX(-50%)\"; break;\n default: // top-right\n s.top = \"1rem\"; s.right = \"1rem\"; break;\n }\n}\n\nfunction createToastElement(msg: ToastMessage): HTMLElement {\n const el = document.createElement(\"div\");\n el.setAttribute(\"role\", \"alert\");\n el.setAttribute(\"data-lvt-toast-item\", msg.id);\n if (msg.type) el.setAttribute(\"data-type\", msg.type);\n\n const inner = document.createElement(\"div\");\n inner.setAttribute(\"data-lvt-toast-content\", \"\");\n\n if (msg.title) {\n const t = document.createElement(\"strong\");\n t.textContent = msg.title;\n inner.appendChild(t);\n }\n if (msg.body) {\n const b = document.createElement(\"p\");\n b.textContent = msg.body;\n inner.appendChild(b);\n }\n\n el.appendChild(inner);\n\n if (msg.dismissible) {\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.setAttribute(\"aria-label\", \"Dismiss\");\n btn.textContent = \"\u00D7\";\n btn.addEventListener(\"click\", () => el.remove());\n el.appendChild(btn);\n }\n\n return el;\n}\n\n", "/**\n * Debounce function: delays execution until after a pause in calls\n */\nexport function debounce<T extends (...args: any[]) => any>(\n func: T,\n wait: number\n): (...args: Parameters<T>) => void {\n let timeout: number | null = null;\n\n return function debounceWrapper(this: unknown, ...args: Parameters<T>) {\n const context = this;\n\n if (timeout !== null) {\n clearTimeout(timeout);\n }\n\n timeout = window.setTimeout(() => {\n func.apply(context, args);\n }, wait);\n };\n}\n\n/**\n * Throttle function: limits execution to at most once per time period\n * First call executes immediately, subsequent calls are delayed\n */\nexport function throttle<T extends (...args: any[]) => any>(\n func: T,\n limit: number\n): (...args: Parameters<T>) => void {\n let inThrottle = false;\n\n return function throttleWrapper(this: unknown, ...args: Parameters<T>) {\n const context = this;\n\n if (!inThrottle) {\n func.apply(context, args);\n inThrottle = true;\n\n setTimeout(() => {\n inThrottle = false;\n }, limit);\n }\n };\n}\n", "/** Escapes colons in attribute names for use in CSS attribute selectors. */\nexport function lvtSelector(attr: string, value?: string): string {\n const escaped = attr.replace(/:/g, \"\\\\:\");\n if (value === undefined) return `[${escaped}]`;\n // Escape backslashes and double-quotes in the value to prevent CSS selector injection\n const safeValue = value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n return `[${escaped}=\"${safeValue}\"]`;\n}\n", "import { debounce, throttle } from \"../utils/rate-limit\";\nimport { lvtSelector } from \"../utils/lvt-selector\";\nimport { executeAction, resolveTarget, processElementInteraction, isDOMEventTrigger, type ReactiveAction } from \"./reactive-attributes\";\nimport type { Logger } from \"../utils/logger\";\n\n// Methods supported by click-away, derived from ReactiveAction values\nconst CLICK_AWAY_METHOD_MAP: Record<string, ReactiveAction> = {\n reset: \"reset\",\n addclass: \"addClass\",\n removeclass: \"removeClass\",\n toggleclass: \"toggleClass\",\n setattr: \"setAttr\",\n toggleattr: \"toggleAttr\",\n};\nconst CLICK_AWAY_METHODS = Object.keys(CLICK_AWAY_METHOD_MAP);\n\n// Non-bubbling events need direct attachment rather than wrapper delegation\nconst NON_BUBBLING = new Set([\"mouseenter\", \"mouseleave\", \"focus\", \"blur\"]);\n\nexport interface EventDelegationContext {\n getWrapperElement(): Element | null;\n getRateLimitedHandlers(): WeakMap<Element, Map<string, Function>>;\n parseValue(value: string): any;\n send(message: any): void;\n sendHTTPMultipart(form: HTMLFormElement, action: string, formData: FormData): void;\n setActiveSubmission(\n form: HTMLFormElement | null,\n button: HTMLButtonElement | null,\n originalButtonText: string | null\n ): void;\n getWebSocketReadyState(): number | undefined;\n triggerPendingUploads(uploadName: string): void;\n}\n\n/**\n * Handles all DOM event delegation concerns for LiveTemplateClient.\n */\n// All event types registered by setupEventDelegation. Exported so\n// teardownForWrapper() can remove them all without drift.\nexport const DELEGATED_EVENT_TYPES = [\n \"click\",\n \"submit\",\n \"change\",\n \"input\",\n \"search\",\n \"keydown\",\n \"keyup\",\n \"focus\",\n \"blur\",\n \"mouseenter\",\n \"mouseleave\",\n] as const;\n\nexport class EventDelegator {\n constructor(\n private readonly context: EventDelegationContext,\n private readonly logger: Logger\n ) {}\n\n /**\n * Remove all document-level event listeners registered by\n * setupEventDelegation for a specific wrapper ID. Call this before\n * a cross-handler navigation changes the wrapper's data-lvt-id, to\n * prevent listener leaks.\n */\n teardownForWrapper(wrapperId: string | null): void {\n if (!wrapperId) return;\n for (const eventType of DELEGATED_EVENT_TYPES) {\n const listenerKey = `__lvt_delegated_${eventType}_${wrapperId}`;\n const existingListener = (document as any)[listenerKey];\n if (existingListener) {\n document.removeEventListener(eventType, existingListener, false);\n delete (document as any)[listenerKey];\n }\n }\n }\n\n private extractButtonData(button: HTMLButtonElement | HTMLInputElement, data: Record<string, any>): void {\n if (button.value) {\n data.value = this.context.parseValue(button.value);\n }\n Array.from(button.attributes).forEach((attr) => {\n if (attr.name.startsWith(\"data-\") && attr.name !== \"data-key\") {\n const key = attr.name.slice(5);\n data[key] = this.context.parseValue(attr.value);\n }\n });\n }\n\n setupEventDelegation(): void {\n const wrapperElement = this.context.getWrapperElement();\n if (!wrapperElement) return;\n\n const eventTypes = DELEGATED_EVENT_TYPES;\n const wrapperId = wrapperElement.getAttribute(\"data-lvt-id\");\n const rateLimitedHandlers = this.context.getRateLimitedHandlers();\n\n eventTypes.forEach((eventType) => {\n const listenerKey = `__lvt_delegated_${eventType}_${wrapperId}`;\n const existingListener = (document as any)[listenerKey];\n if (existingListener) {\n document.removeEventListener(eventType, existingListener, false);\n }\n\n const listener = (e: Event) => {\n const currentWrapper = this.context.getWrapperElement();\n if (!currentWrapper) return;\n\n const target = e.target as Element;\n\n this.logger.debug(\"Event listener triggered:\", eventType, e.target);\n\n if (!target) return;\n\n let element: Element | null = target;\n let inWrapper = false;\n\n while (element) {\n if (element === currentWrapper) {\n inWrapper = true;\n break;\n }\n element = element.parentElement;\n }\n\n if (!inWrapper) return;\n\n const attrName = `lvt-on:${eventType}`;\n element = target;\n\n while (element && element !== currentWrapper.parentElement) {\n let action = element.getAttribute(attrName);\n let actionElement = element;\n let isOrphanButton = false;\n\n // Check for lvt-persist on form submit (auto-persist to database)\n if (!action && eventType === \"submit\" && element instanceof HTMLFormElement) {\n const persistTable = element.getAttribute(\"lvt-persist\");\n if (persistTable) {\n action = `persist:${persistTable}`;\n actionElement = element;\n }\n }\n\n // Orphan button detection (Tier 1: formless standalone buttons).\n // A <button name=\"action\"> outside any form triggers the named action directly.\n // Resolution order for click events:\n // 1. lvt-on:click attribute (Tier 2 \u2014 already checked above)\n // 2. Orphan button name (Tier 1 \u2014 checked here)\n if (!action && eventType === \"click\") {\n const btn = element instanceof HTMLButtonElement ? element : null;\n if (\n btn &&\n btn.name &&\n !btn.disabled &&\n btn.type !== \"reset\" &&\n btn.form === null &&\n !btn.hasAttribute(\"commandfor\")\n ) {\n action = btn.name;\n actionElement = btn;\n isOrphanButton = true;\n }\n }\n\n // Auto-intercept forms (progressive complexity).\n // Action resolution order:\n // 1. lvt-form:action attribute (explicit routing)\n // 2. submitter.name (button name = action)\n // 3. form.name (form name = action)\n // 4. \"submit\" (server defaults to Submit())\n //\n // Note: lvt-action hidden field is a server-side progressive\n // enhancement fallback (no-JS POST). The client does not read it;\n // the server extracts it from form data directly.\n if (!action && eventType === \"submit\" && element instanceof HTMLFormElement) {\n if (!element.hasAttribute(\"lvt-form:no-intercept\")) {\n // Check for explicit routing attribute first.\n // Empty string (\"\") falls through to submitter/form name resolution.\n const explicitAction = element.getAttribute(\"lvt-form:action\");\n const submitter = (e as SubmitEvent).submitter;\n if (explicitAction) {\n action = explicitAction;\n } else {\n if (submitter instanceof HTMLButtonElement && submitter.name) {\n action = submitter.name;\n } else if (element.getAttribute(\"name\")) {\n action = element.getAttribute(\"name\")!;\n } else {\n action = \"submit\";\n }\n }\n actionElement = element;\n\n if (submitter) {\n (element as any).__lvtSubmitter = submitter;\n }\n\n // Dialog support: forms with method=\"dialog\" inside <dialog>\n // close the dialog AND route the action to the server.\n const dialog = element.closest(\"dialog\");\n if (dialog && element.getAttribute(\"method\")?.toLowerCase() === \"dialog\") {\n (element as any).__lvtCloseDialog = dialog;\n }\n }\n }\n\n if (action != null && actionElement) {\n if (eventType === \"submit\") {\n e.preventDefault();\n }\n\n if (\n (eventType === \"keydown\" || eventType === \"keyup\") &&\n actionElement.hasAttribute(\"lvt-key\")\n ) {\n const keyFilter = actionElement.getAttribute(\"lvt-key\");\n const keyboardEvent = e as KeyboardEvent;\n if (keyFilter && keyboardEvent.key !== keyFilter) {\n element = element.parentElement;\n continue;\n }\n }\n\n const targetElement = actionElement;\n\n const handleAction = () => {\n this.logger.debug(\"handleAction called\", {\n action,\n eventType,\n targetElement,\n });\n\n const message: any = { action, data: {} };\n\n if (targetElement instanceof HTMLFormElement) {\n this.logger.debug(\"Processing form element\");\n const formData = new FormData(targetElement);\n\n const checkboxes = Array.from(\n targetElement.querySelectorAll('input[type=\"checkbox\"][name]')\n ) as HTMLInputElement[];\n\n const checkboxGroups = new Map<string, HTMLInputElement[]>();\n checkboxes.forEach((cb) => {\n const group = checkboxGroups.get(cb.name) || [];\n group.push(cb);\n checkboxGroups.set(cb.name, group);\n });\n\n checkboxGroups.forEach((cbs, name) => {\n if (cbs.length === 1) {\n message.data[name] = cbs[0].checked;\n } else {\n message.data[name] = cbs.filter(cb => cb.checked).map(cb => cb.value);\n }\n });\n\n // Get password field names to skip parseValue for them\n const passwordFields = new Set(\n Array.from(\n targetElement.querySelectorAll('input[type=\"password\"][name]')\n ).map((el) => (el as HTMLInputElement).name)\n );\n\n // Exclude the submitter button's name from form data.\n // The submitter's name is used as the action routing key in the\n // button-name path \u2014 including it in data would be redundant.\n // When lvt-form:action overrides routing, the button name is still\n // excluded to avoid noisy payloads (the button is a UI control,\n // not domain data). Button value and data-* attrs are collected below.\n // \"action\" is NOT excluded \u2014 it's a normal data field.\n const submitterForData = (targetElement as any).__lvtSubmitter as HTMLButtonElement | undefined;\n const actionFieldName = submitterForData?.name;\n\n formData.forEach((value, key) => {\n if (value instanceof File) return; // Skip file entries \u2014 handled by sendHTTPMultipart\n if (actionFieldName && key === actionFieldName) return;\n if (checkboxGroups.has(key)) return;\n if (passwordFields.has(key)) {\n // Never parse password values - always keep as string\n message.data[key] = value as string;\n } else {\n message.data[key] = this.context.parseValue(\n value as string\n );\n }\n });\n\n // Collect data from the submitter button:\n // - button value \u2192 data.value (e.g., <button name=\"delete\" value=\"{{.ID}}\">)\n // - data-* attributes \u2192 data keys\n const submitter2 = (targetElement as any).__lvtSubmitter as HTMLButtonElement | undefined;\n if (submitter2) {\n this.extractButtonData(submitter2, message.data);\n delete (targetElement as any).__lvtSubmitter;\n }\n\n this.logger.debug(\"Form data collected:\", message.data);\n } else if (eventType === \"change\" || eventType === \"input\" || eventType === \"search\") {\n if (targetElement instanceof HTMLInputElement) {\n const key = targetElement.name || \"value\";\n message.data[key] = this.context.parseValue(\n targetElement.value\n );\n } else if (targetElement instanceof HTMLSelectElement) {\n const key = targetElement.name || \"value\";\n message.data[key] = this.context.parseValue(\n targetElement.value\n );\n } else if (targetElement instanceof HTMLTextAreaElement) {\n const key = targetElement.name || \"value\";\n message.data[key] = this.context.parseValue(\n targetElement.value\n );\n }\n }\n\n if (isOrphanButton) {\n this.extractButtonData(actionElement as HTMLButtonElement, message.data);\n }\n\n // Extract standard data-* attributes from the action element.\n // Exclude data-key (list reconciliation) and data-lvt-id (internal framework ID)\n // since these are LiveTemplate internals, not user-provided action data.\n if (!(targetElement instanceof HTMLFormElement) && !isOrphanButton) {\n Array.from(actionElement.attributes).forEach((attr) => {\n if (attr.name.startsWith(\"data-\") && attr.name !== \"data-key\" && attr.name !== \"data-lvt-id\") {\n const key = attr.name.slice(5);\n message.data[key] = this.context.parseValue(attr.value);\n }\n });\n }\n\n // Tier 1 file upload detection \u2014 must happen BEFORE setActiveSubmission\n // which disables the fieldset. Once the fieldset is disabled, FormData\n // excludes all its child fields, so we must build FormData here if we\n // need it for HTTP multipart upload.\n let tier1FormData: FormData | null = null;\n if (\n eventType === \"submit\" &&\n targetElement instanceof HTMLFormElement\n ) {\n const tier1FileInputs = targetElement.querySelectorAll<HTMLInputElement>(\n 'input[type=\"file\"]:not([lvt-upload])'\n );\n const hasFiles = Array.from(tier1FileInputs).some(\n (input) => input.files && input.files.length > 0\n );\n if (hasFiles) {\n // Include the submitter's name/value so multi-button\n // forms preserve the clicked button's entry (e.g.\n // <button name=\"action\" value=\"save\">). We avoid the\n // two-argument FormData constructor (Chrome 112+,\n // Firefox 111+, Safari 16.4+ \u2014 March 2023) and set\n // the submitter value manually for broader browser\n // compatibility.\n const submitter = (e as SubmitEvent).submitter as\n | HTMLButtonElement\n | HTMLInputElement\n | null;\n tier1FormData = new FormData(targetElement);\n if (submitter?.name) {\n tier1FormData.set(submitter.name, submitter.value);\n }\n // Set the action field here (not inside sendHTTPMultipart)\n // so the client-owned FormData is fully prepared before\n // being passed to another method. This keeps\n // sendHTTPMultipart from mutating a caller-supplied\n // FormData object.\n tier1FormData.set(\"lvt-action\", action);\n }\n }\n\n if (\n eventType === \"submit\" &&\n targetElement instanceof HTMLFormElement\n ) {\n const submitEvent = e as SubmitEvent;\n const submitButton =\n submitEvent.submitter as HTMLButtonElement | null;\n let originalButtonText: string | null = null;\n\n if (\n submitButton &&\n submitButton.hasAttribute(\"lvt-form:disable-with\")\n ) {\n originalButtonText = submitButton.textContent;\n submitButton.disabled = true;\n submitButton.textContent =\n submitButton.getAttribute(\"lvt-form:disable-with\");\n this.logger.debug(\"Disabled submit button\");\n }\n\n this.context.setActiveSubmission(\n targetElement,\n submitButton || null,\n originalButtonText\n );\n\n // Trigger pending uploads for any file inputs in the form\n const fileInputs = targetElement.querySelectorAll<HTMLInputElement>(\n 'input[type=\"file\"][lvt-upload]'\n );\n fileInputs.forEach((input) => {\n const uploadName = input.getAttribute(\"lvt-upload\");\n if (uploadName) {\n this.logger.debug(\"Triggering pending uploads for:\", uploadName);\n this.context.triggerPendingUploads(uploadName);\n }\n });\n\n targetElement.dispatchEvent(\n new CustomEvent(\"lvt:pending\", { detail: message })\n );\n this.logger.debug(\"Emitted lvt:pending event\");\n }\n\n this.logger.debug(\"About to send message:\", message);\n this.logger.debug(\n \"WebSocket state:\",\n this.context.getWebSocketReadyState()\n );\n\n // Tier 1 file uploads: send via HTTP fetch with FormData captured\n // BEFORE the fieldset was disabled by setActiveSubmission.\n // tier1FormData is only set when targetElement is a form.\n if (tier1FormData !== null) {\n this.logger.debug(\"Tier 1 file upload detected, using HTTP fetch\");\n this.context.sendHTTPMultipart(\n targetElement as HTMLFormElement,\n action,\n tier1FormData\n );\n return;\n }\n\n this.context.send(message);\n this.logger.debug(\"send() called\");\n\n // Close dialog if this was a method=\"dialog\" form inside <dialog>\n if (targetElement instanceof HTMLFormElement) {\n const dialogToClose = (targetElement as any).__lvtCloseDialog as HTMLDialogElement | undefined;\n if (dialogToClose) {\n dialogToClose.close();\n delete (targetElement as any).__lvtCloseDialog;\n }\n }\n };\n\n const throttleValue = actionElement.getAttribute(\"lvt-mod:throttle\");\n const debounceValue = actionElement.getAttribute(\"lvt-mod:debounce\");\n\n // Skip rate limiting for \"search\" event (clear button click) - it's a discrete action\n const shouldRateLimit = (throttleValue || debounceValue) && eventType !== \"search\";\n\n if (shouldRateLimit) {\n if (!rateLimitedHandlers.has(actionElement)) {\n rateLimitedHandlers.set(actionElement, new Map());\n }\n const handlerCache = rateLimitedHandlers.get(actionElement)!;\n const cacheKey = `${eventType}:${action}`;\n\n // Store callback reference on the element itself to avoid type issues with the Map\n // This allows us to update the callback each event while reusing the debounced timer\n const callbackRefKey = `__lvt_callback_${cacheKey}`;\n const elementWithCallback = actionElement as HTMLElement & { [key: string]: { current: () => void } };\n if (!elementWithCallback[callbackRefKey]) {\n elementWithCallback[callbackRefKey] = { current: handleAction };\n }\n // Always update to the latest handleAction (with fresh closure capturing current values)\n elementWithCallback[callbackRefKey].current = handleAction;\n\n let rateLimitedHandler = handlerCache.get(cacheKey);\n if (!rateLimitedHandler) {\n // Create rate-limited function that calls the CURRENT callback via reference\n // This way, when the debounce timer fires, it uses the latest captured values\n const callLatest = () => elementWithCallback[callbackRefKey].current();\n if (throttleValue) {\n const limit = parseInt(throttleValue, 10);\n rateLimitedHandler = throttle(callLatest, limit);\n } else if (debounceValue) {\n const wait = parseInt(debounceValue, 10);\n rateLimitedHandler = debounce(callLatest, wait);\n }\n if (rateLimitedHandler) {\n handlerCache.set(cacheKey, rateLimitedHandler);\n }\n }\n\n if (rateLimitedHandler) {\n rateLimitedHandler();\n }\n } else {\n handleAction();\n }\n\n return;\n }\n element = element.parentElement;\n }\n };\n\n (document as any)[listenerKey] = listener;\n document.addEventListener(eventType, listener, false);\n this.logger.debug(\n \"Registered event listener:\",\n eventType,\n \"with key:\",\n listenerKey\n );\n });\n }\n\n setupWindowEventDelegation(): void {\n const wrapperElement = this.context.getWrapperElement();\n if (!wrapperElement) return;\n\n const windowEvents = [\n \"keydown\",\n \"keyup\",\n \"scroll\",\n \"resize\",\n \"focus\",\n \"blur\",\n ];\n const wrapperId = wrapperElement.getAttribute(\"data-lvt-id\");\n const rateLimitedHandlers = this.context.getRateLimitedHandlers();\n\n windowEvents.forEach((eventType) => {\n const listenerKey = `__lvt_window_${eventType}_${wrapperId}`;\n const existingListener = (window as any)[listenerKey];\n if (existingListener) {\n window.removeEventListener(eventType, existingListener);\n }\n\n const listener = (e: Event) => {\n const currentWrapper = this.context.getWrapperElement();\n if (!currentWrapper) return;\n\n const attrName = `lvt-on:window:${eventType}`;\n const elements = currentWrapper.querySelectorAll(lvtSelector(attrName));\n\n elements.forEach((element) => {\n const action = element.getAttribute(attrName);\n if (!action) return;\n\n if (\n (eventType === \"keydown\" || eventType === \"keyup\") &&\n element.hasAttribute(\"lvt-key\")\n ) {\n const keyFilter = element.getAttribute(\"lvt-key\");\n const keyboardEvent = e as KeyboardEvent;\n if (keyFilter && keyboardEvent.key !== keyFilter) {\n return;\n }\n }\n\n const message: any = { action, data: {} };\n\n // Extract standard data-* attributes from element\n Array.from(element.attributes).forEach((attr) => {\n if (attr.name.startsWith(\"data-\") && attr.name !== \"data-key\" && attr.name !== \"data-lvt-id\") {\n const key = attr.name.slice(5);\n message.data[key] = this.context.parseValue(attr.value);\n }\n });\n\n const throttleValue = element.getAttribute(\"lvt-mod:throttle\");\n const debounceValue = element.getAttribute(\"lvt-mod:debounce\");\n\n const handleAction = () => this.context.send(message);\n\n if (throttleValue || debounceValue) {\n if (!rateLimitedHandlers.has(element)) {\n rateLimitedHandlers.set(element, new Map());\n }\n const handlerCache = rateLimitedHandlers.get(element)!;\n const cacheKey = `window-${eventType}:${action}`;\n\n let rateLimitedHandler = handlerCache.get(cacheKey);\n if (!rateLimitedHandler) {\n if (throttleValue) {\n const limit = parseInt(throttleValue, 10);\n rateLimitedHandler = throttle(handleAction, limit);\n } else if (debounceValue) {\n const wait = parseInt(debounceValue, 10);\n rateLimitedHandler = debounce(handleAction, wait);\n }\n if (rateLimitedHandler) {\n handlerCache.set(cacheKey, rateLimitedHandler);\n }\n }\n\n if (rateLimitedHandler) {\n rateLimitedHandler();\n }\n } else {\n handleAction();\n }\n });\n };\n\n (window as any)[listenerKey] = listener;\n window.addEventListener(eventType, listener);\n });\n }\n\n /**\n * Sets up click-away detection for lvt-el:*:on:click-away attributes.\n * Instead of routing to a server action, click-away triggers client-side\n * DOM manipulation via executeAction from reactive-attributes.\n */\n setupClickAwayDelegation(): void {\n const wrapperElement = this.context.getWrapperElement();\n if (!wrapperElement) return;\n\n const wrapperId = wrapperElement.getAttribute(\"data-lvt-id\");\n const listenerKey = `__lvt_click_away_${wrapperId}`;\n const existingListener = (document as any)[listenerKey];\n if (existingListener) {\n document.removeEventListener(\"click\", existingListener);\n }\n\n const listener = (e: Event) => {\n const currentWrapper = this.context.getWrapperElement();\n if (!currentWrapper) return;\n\n const target = e.target as Element;\n\n const clickAwaySelector = CLICK_AWAY_METHODS\n .map(m => lvtSelector(`lvt-el:${m}:on:click-away`))\n .join(\", \");\n const clickAwayElements = currentWrapper.querySelectorAll(clickAwaySelector);\n clickAwayElements.forEach((element) => {\n if (element.contains(target)) return; // Click was inside, not away\n\n Array.from(element.attributes).forEach((attr) => {\n if (!attr.name.includes(\":on:click-away\")) return;\n const match = attr.name.match(/^lvt-el:(\\w+):on:click-away$/);\n if (!match) return;\n const method = CLICK_AWAY_METHOD_MAP[match[1].toLowerCase()];\n if (!method) return;\n executeAction(resolveTarget(element), method, attr.value);\n });\n });\n };\n\n (document as any)[listenerKey] = listener;\n document.addEventListener(\"click\", listener);\n }\n\n /**\n * Sets up event listeners for lvt-el:*:on:{event} attributes where {event}\n * is a native DOM event (not a lifecycle state or synthetic trigger).\n *\n * Scans scanRoot (or the full wrapper if omitted) for elements with these\n * attributes. Attaches direct listeners for non-bubbling events (mouseenter,\n * mouseleave) and delegated listeners on the wrapper for bubbling events\n * (click, focusin, focusout, etc.).\n *\n * Bubbling delegation uses closest-match semantics: if both a child and parent\n * have the same trigger, only the child's action fires. This differs from native\n * event bubbling and prevents unintended double-firing in nested structures.\n *\n * Called during connect and after each DOM update to handle new elements.\n *\n * @param scanRoot - Subtree to scan for new attributes. Defaults to full wrapper.\n * Pass the updated element after a DOM patch to avoid a full rescan.\n */\n setupDOMEventTriggerDelegation(scanRoot?: Element): void {\n const wrapperElement = this.context.getWrapperElement();\n if (!wrapperElement) return;\n\n const wrapperId = wrapperElement.getAttribute(\"data-lvt-id\");\n if (!wrapperId) return;\n // Track which bubbling events we've already delegated at wrapper level\n const delegatedKey = `__lvt_el_delegated_${wrapperId}`;\n const delegated: Set<string> = (wrapperElement as any)[delegatedKey] || new Set();\n\n // Track all listeners (direct + delegated) on wrapper for teardown\n // Prune stale entries from elements replaced by morphdom\n const listenersKey = `__lvt_el_listeners_${wrapperId}`;\n const allListeners: Array<{ el: Element; event: string; handler: EventListener; guardKey?: string }> =\n ((wrapperElement as any)[listenersKey] || []).filter(\n (entry: { el: Element }) => entry.el.isConnected\n );\n\n // Scan the provided subtree (or full wrapper) for lvt-el:*:on:{event} attributes.\n // Process root then descendants (avoids spreading NodeList into array).\n const root = scanRoot || wrapperElement;\n const processEl = (el: Element) => {\n const triggers = new Set<string>();\n for (const attr of el.attributes) {\n if (!attr.name.startsWith(\"lvt-el:\")) continue;\n const match = attr.name.match(/^lvt-el:\\w+:on:([a-z-]+)$/i);\n if (!match) continue;\n const trigger = match[1].toLowerCase();\n if (!isDOMEventTrigger(trigger)) continue;\n triggers.add(trigger);\n }\n\n for (const trigger of triggers) {\n if (NON_BUBBLING.has(trigger)) {\n // Direct attachment for non-bubbling events\n const key = `__lvt_el_${trigger}`;\n if ((el as any)[key]) continue; // already attached\n const listener = () => processElementInteraction(el, trigger);\n el.addEventListener(trigger, listener);\n (el as any)[key] = listener;\n allListeners.push({ el, event: trigger, handler: listener, guardKey: key });\n } else if (!delegated.has(trigger)) {\n // Delegated listener on wrapper for bubbling events.\n // Walks from target to wrapper, processing only the closest matching element.\n const escaped = trigger.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const triggerPattern = new RegExp(`^lvt-el:\\\\w+:on:${escaped}$`, \"i\");\n const handler = (e: Event) => {\n let target = e.target as Element | null;\n while (target && target !== wrapperElement) {\n let hasMatch = false;\n for (const a of target.attributes) {\n if (triggerPattern.test(a.name)) { hasMatch = true; break; }\n }\n if (hasMatch) {\n processElementInteraction(target, trigger);\n return; // Stop at closest match\n }\n target = target.parentElement;\n }\n // Also check wrapper itself\n if (target === wrapperElement) {\n processElementInteraction(wrapperElement, trigger);\n }\n };\n wrapperElement.addEventListener(trigger, handler);\n delegated.add(trigger);\n allListeners.push({ el: wrapperElement, event: trigger, handler });\n }\n }\n };\n processEl(root);\n root.querySelectorAll(\"*\").forEach(processEl);\n\n (wrapperElement as any)[listenersKey] = allListeners;\n (wrapperElement as any)[delegatedKey] = delegated;\n }\n\n /**\n * Remove delegated DOM event trigger listeners added by setupDOMEventTriggerDelegation.\n * Call on disconnect to prevent stale listeners firing on a disconnected component.\n */\n teardownDOMEventTriggerDelegation(): void {\n const wrapperElement = this.context.getWrapperElement();\n if (!wrapperElement) return;\n\n const wrapperId = wrapperElement.getAttribute(\"data-lvt-id\");\n if (!wrapperId) return;\n\n const listenersKey = `__lvt_el_listeners_${wrapperId}`;\n const listeners: Array<{ el: Element; event: string; handler: EventListener; guardKey?: string }> | undefined =\n (wrapperElement as any)[listenersKey];\n if (listeners) {\n listeners.forEach(({ el, event, handler, guardKey }) => {\n el.removeEventListener(event, handler);\n if (guardKey) delete (el as any)[guardKey];\n });\n delete (wrapperElement as any)[listenersKey];\n }\n\n const delegatedKey = `__lvt_el_delegated_${wrapperId}`;\n delete (wrapperElement as any)[delegatedKey];\n }\n\n /**\n * Sets up focus trapping for elements with lvt-focus-trap attribute.\n * Focus is trapped within the element, cycling through focusable elements\n * when Tab/Shift+Tab is pressed.\n */\n setupFocusTrapDelegation(): void {\n const wrapperElement = this.context.getWrapperElement();\n if (!wrapperElement) return;\n\n const wrapperId = wrapperElement.getAttribute(\"data-lvt-id\");\n const listenerKey = `__lvt_focus_trap_${wrapperId}`;\n const existingListener = (document as any)[listenerKey];\n if (existingListener) {\n document.removeEventListener(\"keydown\", existingListener);\n }\n\n const getFocusableElements = (container: Element): HTMLElement[] => {\n const selector = [\n 'a[href]:not([disabled])',\n 'button:not([disabled])',\n 'textarea:not([disabled])',\n 'input:not([disabled]):not([type=\"hidden\"])',\n 'select:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"]):not([disabled])',\n '[contenteditable=\"true\"]'\n ].join(', ');\n\n return Array.from(container.querySelectorAll(selector)).filter(\n (el) => {\n const htmlEl = el as HTMLElement;\n // Check if element is visible\n const style = window.getComputedStyle(htmlEl);\n const isNotDisplayNone = style.display !== 'none';\n const isNotVisibilityHidden = style.visibility !== 'hidden';\n // offsetParent can be null in JSDOM or for fixed/absolute positioned elements\n const hasLayoutContext = htmlEl.offsetParent !== null ||\n style.position === 'fixed' ||\n style.position === 'absolute' ||\n // In test environments, offsetParent may always be null\n (typeof process !== 'undefined' && (process as any).env?.NODE_ENV === 'test');\n return isNotDisplayNone && isNotVisibilityHidden && hasLayoutContext;\n }\n ) as HTMLElement[];\n };\n\n const listener = (e: KeyboardEvent) => {\n if (e.key !== \"Tab\") return;\n\n const currentWrapper = this.context.getWrapperElement();\n if (!currentWrapper) return;\n\n // Find the active focus trap container (innermost one containing the focused element)\n const focusTrapElements = currentWrapper.querySelectorAll(\"[lvt-focus-trap]\");\n let activeTrap: Element | null = null;\n\n focusTrapElements.forEach((trap) => {\n if (trap.contains(document.activeElement)) {\n // Check if this is the innermost trap containing the focused element\n if (!activeTrap || trap.contains(activeTrap)) {\n activeTrap = trap;\n }\n }\n });\n\n // If there's a visible focus trap that doesn't contain the active element,\n // and is visible, trap focus there (for newly opened modals/dropdowns)\n if (!activeTrap) {\n focusTrapElements.forEach((trap) => {\n const htmlTrap = trap as HTMLElement;\n const style = window.getComputedStyle(htmlTrap);\n if (style.display !== 'none' && style.visibility !== 'hidden') {\n activeTrap = trap;\n }\n });\n }\n\n if (!activeTrap) return;\n\n const focusableElements = getFocusableElements(activeTrap);\n if (focusableElements.length === 0) return;\n\n const firstElement = focusableElements[0];\n const lastElement = focusableElements[focusableElements.length - 1];\n\n if (e.shiftKey) {\n // Shift+Tab: moving backwards\n if (document.activeElement === firstElement) {\n e.preventDefault();\n lastElement.focus();\n }\n } else {\n // Tab: moving forwards\n if (document.activeElement === lastElement) {\n e.preventDefault();\n firstElement.focus();\n }\n }\n };\n\n (document as any)[listenerKey] = listener;\n document.addEventListener(\"keydown\", listener);\n this.logger.debug(\"Focus trap delegation set up\");\n }\n\n /**\n * Sets up autofocus for elements with lvt-autofocus attribute.\n * Automatically focuses the first element with lvt-autofocus when it becomes visible.\n * Uses MutationObserver to detect when elements are added or become visible.\n */\n setupAutofocusDelegation(): void {\n const wrapperElement = this.context.getWrapperElement();\n if (!wrapperElement) return;\n\n const wrapperId = wrapperElement.getAttribute(\"data-lvt-id\");\n const observerKey = `__lvt_autofocus_observer_${wrapperId}`;\n\n // Disconnect existing observer if any\n const existingObserver = (wrapperElement as any)[observerKey];\n if (existingObserver) {\n existingObserver.disconnect();\n }\n\n const processAutofocus = () => {\n const currentWrapper = this.context.getWrapperElement();\n if (!currentWrapper) return;\n\n // Find all elements with lvt-autofocus that are visible\n const autofocusElements = currentWrapper.querySelectorAll(\"[lvt-autofocus]\");\n\n autofocusElements.forEach((element) => {\n const htmlElement = element as HTMLElement;\n const style = window.getComputedStyle(htmlElement);\n\n // Check if element is visible and hasn't been focused yet in this visibility state\n // Note: offsetParent can be null in JSDOM or for fixed/absolute positioned elements,\n // so we only use it as a secondary check when it's available\n const isNotDisplayNone = style.display !== 'none';\n const isNotVisibilityHidden = style.visibility !== 'hidden';\n const hasLayoutContext = htmlElement.offsetParent !== null ||\n style.position === 'fixed' ||\n style.position === 'absolute' ||\n htmlElement.tagName === 'BODY' ||\n // In test environments, offsetParent may always be null\n (typeof process !== 'undefined' && (process as any).env?.NODE_ENV === 'test');\n const isVisible = isNotDisplayNone && isNotVisibilityHidden && hasLayoutContext;\n\n const wasFocused = htmlElement.getAttribute(\"data-lvt-autofocused\") === \"true\";\n\n if (isVisible && !wasFocused) {\n // Mark as focused to prevent re-focusing on every mutation\n htmlElement.setAttribute(\"data-lvt-autofocused\", \"true\");\n\n // Use requestAnimationFrame to ensure DOM is ready\n requestAnimationFrame(() => {\n htmlElement.focus();\n this.logger.debug(\"Autofocused element:\", htmlElement.tagName, htmlElement.id || htmlElement.getAttribute(\"name\"));\n });\n } else if (!isVisible && wasFocused) {\n // Reset the flag when element becomes hidden so it can be refocused when shown again\n htmlElement.removeAttribute(\"data-lvt-autofocused\");\n }\n });\n };\n\n // Process autofocus immediately for any existing elements\n processAutofocus();\n\n // Set up MutationObserver to watch for new autofocus elements or visibility changes\n const observer = new MutationObserver((mutations) => {\n let shouldProcess = false;\n\n mutations.forEach((mutation) => {\n // Check for added nodes\n if (mutation.type === \"childList\" && mutation.addedNodes.length > 0) {\n mutation.addedNodes.forEach((node) => {\n if (node instanceof Element) {\n if (node.hasAttribute(\"lvt-autofocus\") || node.querySelector(\"[lvt-autofocus]\")) {\n shouldProcess = true;\n }\n }\n });\n }\n\n // Check for attribute changes that might affect visibility\n // We intentionally check \"class\" changes to handle CSS-based visibility\n // (e.g., Tailwind's \"hidden\" class, Bootstrap's \"d-none\", etc.)\n // This may cause some extra processing but ensures visibility changes\n // via class toggles are detected.\n if (mutation.type === \"attributes\") {\n const target = mutation.target as Element;\n if (target.hasAttribute(\"lvt-autofocus\") ||\n mutation.attributeName === \"hidden\" ||\n mutation.attributeName === \"style\" ||\n mutation.attributeName === \"class\") {\n shouldProcess = true;\n }\n }\n });\n\n if (shouldProcess) {\n processAutofocus();\n }\n });\n\n observer.observe(wrapperElement, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: [\"hidden\", \"style\", \"class\", \"lvt-autofocus\"]\n });\n\n (wrapperElement as any)[observerKey] = observer;\n this.logger.debug(\"Autofocus delegation set up\");\n }\n\n}\n", "import type { Logger } from \"../utils/logger\";\n\nexport interface LinkInterceptorContext {\n getWrapperElement(): Element | null;\n handleNavigationResponse(html: string): void;\n // Send an in-band navigate message over the existing WebSocket.\n // Returns true if the message was sent, false if it was dropped\n // (e.g. WS not open). The caller uses this to decide whether to push\n // browser history state \u2014 only advancing the URL when the server will\n // actually receive the navigate eliminates the TOCTOU window where\n // the WS could close between canSendNavigate() and the actual send.\n sendNavigate(href: string): boolean;\n // Returns true when an in-band navigate message can be sent (i.e.\n // WebSocket mode is active and the socket is OPEN). In HTTP mode or\n // when the WS is not yet open, this is false and the same-pathname\n // fast path must fall through to a normal fetch.\n canSendNavigate(): boolean;\n}\n\n/**\n * Intercepts <a> clicks within the LiveTemplate wrapper for SPA navigation.\n *\n * - Same pathname (query-string change only) -> sends __navigate__ over WS;\n * no fetch, no DOM replace, no reconnect.\n * - Different pathname (cross-handler or just different route) -> fetches\n * new HTML and hands it to handleNavigationResponse, which decides\n * between same-handler DOM replace and cross-handler reconnect.\n * - External links, target=\"_blank\", download, and lvt-nav:no-intercept\n * are skipped.\n *\n * Uses AbortController to cancel in-flight fetches when a new navigation\n * starts (rapid clicks, back/forward during fetch).\n */\nexport class LinkInterceptor {\n private popstateListener: (() => void) | null = null;\n private abortController: AbortController | null = null;\n // Tracks the URL that was last successfully navigated to (or the initial\n // page URL). Updated after each in-band __navigate__ push and after each\n // fetch-based navigation. The popstate handler uses this to compare the\n // target URL against the URL we were actually at *before* the browser\n // changed window.location, because by the time popstate fires, the browser\n // has already moved window.location to the target \u2014 making a naive\n // window.location comparison always look like a same-URL no-op.\n private currentHref: string = window.location.href;\n\n constructor(\n private readonly context: LinkInterceptorContext,\n private readonly logger: Logger\n ) {}\n\n /**\n * Remove the click listener registered by setup() for a specific\n * wrapper ID. Call this before cross-handler navigation changes the\n * wrapper's data-lvt-id, to prevent orphaned listeners.\n *\n * Also aborts any in-flight navigate() fetch so it cannot call\n * handleNavigationResponse after teardown and trigger a duplicate\n * or out-of-date navigation.\n */\n teardownForWrapper(wrapperId: string | null): void {\n // Abort any in-flight fetch \u2014 whether or not a wrapper ID is passed.\n // The caller may be tearing down before a cross-handler transition,\n // and we don't want a pending fetch to land post-teardown.\n this.abortController?.abort();\n this.abortController = null;\n\n if (!wrapperId) return;\n const listenerKey = `__lvt_link_intercept_${wrapperId}`;\n const existing = (document as any)[listenerKey];\n if (existing) {\n // Explicit capture flag (false) for consistency with\n // EventDelegator.teardownForWrapper \u2014 defaults match but\n // explicit is clearer.\n document.removeEventListener(\"click\", existing, false);\n delete (document as any)[listenerKey];\n }\n }\n\n setup(wrapper: Element): void {\n // Refresh currentHref so the popstate handler compares against the URL\n // that is actually showing when this setup runs, not a stale value from\n // a previous navigation or from construction time (which may predate the\n // first history.replaceState in tests and cross-handler nav re-setups).\n this.currentHref = window.location.href;\n\n const wrapperId = wrapper.getAttribute(\"data-lvt-id\");\n const listenerKey = `__lvt_link_intercept_${wrapperId}`;\n const existing = (document as any)[listenerKey];\n if (existing) {\n document.removeEventListener(\"click\", existing);\n }\n\n const listener = (e: Event) => {\n const target = (e.target as Element)?.closest(\"a[href]\") as HTMLAnchorElement | null;\n if (!target) return;\n\n const currentWrapper = this.context.getWrapperElement();\n if (!currentWrapper || !currentWrapper.contains(target)) return;\n\n if (this.shouldSkip(target)) return;\n\n e.preventDefault();\n this.navigate(target.href);\n };\n\n document.addEventListener(\"click\", listener);\n (document as any)[listenerKey] = listener;\n\n // Handle back/forward navigation\n if (!this.popstateListener) {\n this.popstateListener = () => {\n // Capture the URL we were at *before* the browser moved to the new\n // history entry. This lets navigate() compare pathname+search against\n // the previous URL rather than window.location (which already reflects\n // the target after popstate fires).\n const prevHref = this.currentHref;\n this.currentHref = window.location.href;\n this.navigate(window.location.href, false, prevHref);\n };\n window.addEventListener(\"popstate\", this.popstateListener);\n }\n }\n\n private shouldSkip(link: HTMLAnchorElement): boolean {\n // External links\n if (link.origin !== window.location.origin) return true;\n // target=\"_blank\" or other targets\n if (link.target && link.target !== \"_self\") return true;\n // Download links\n if (link.hasAttribute(\"download\")) return true;\n // Opt-out attribute for link interception\n if (link.hasAttribute(\"lvt-nav:no-intercept\")) return true;\n // Hash-only links (scroll anchors)\n if (link.pathname === window.location.pathname && link.hash) return true;\n // mailto/tel/javascript\n const protocol = link.protocol;\n if (protocol !== \"http:\" && protocol !== \"https:\") return true;\n\n return false;\n }\n\n // prevHref is the URL the client was at *before* this navigation.\n // For link clicks (pushState=true) it defaults to window.location.href,\n // which is correct because pushState hasn't run yet. For popstate\n // (pushState=false) the popstate listener supplies the saved currentHref\n // so the same-pathname comparison reflects the real previous entry, not\n // window.location (which the browser already updated to the target).\n private async navigate(\n href: string,\n pushState: boolean = true,\n prevHref: string = window.location.href\n ): Promise<void> {\n const targetURL = new URL(href, window.location.origin);\n const refURL = new URL(prevHref, window.location.origin);\n const samePath =\n targetURL.origin === refURL.origin &&\n targetURL.pathname === refURL.pathname;\n\n if (samePath) {\n const sameSearch = targetURL.search === refURL.search;\n if (sameSearch) {\n // Hash-only change or exact same URL \u2014 the browser handles scroll\n // to the anchor; no server round-trip is needed. This also correctly\n // handles popstate for hash-only back/forward because the popstate\n // listener passes prevHref (the previous entry), so refURL reflects\n // where we came from rather than the already-updated window.location.\n //\n // Still abort any in-flight cross-path fetch: if a fetch was in\n // progress when the user clicked a hash anchor, we don't want it\n // to resolve and call handleNavigationResponse unexpectedly.\n this.abortController?.abort();\n this.abortController = null;\n return;\n }\n\n // __navigate__ fast path: same pathname, different search, WS mode.\n // Only for explicit forward navigation (pushState=true / link clicks).\n // For popstate (pushState=false) the search difference is real (it\n // compares against the previous entry via prevHref), but back/forward\n // must restore prior page state via a full fetch, not a WS message\n // that only forwards query data to Mount.\n if (pushState && this.context.canSendNavigate()) {\n // Abort any in-flight fetch even on the fast path: a user could\n // click a cross-path link (starting a fetch) and quickly click a\n // same-pathname link. Without aborting, the earlier fetch can\n // still resolve and call handleNavigationResponse, racing with the\n // in-band __navigate__ update.\n this.abortController?.abort();\n this.abortController = null;\n // sendNavigate returns true if the WS message was actually sent.\n // Push history state ONLY on success to keep window.location\n // consistent with what the server received.\n // If sent === false (defensive path \u2014 normally unreachable since\n // canSendNavigate() already checked readyState), fall through to\n // the normal fetch so the navigation isn't silently dropped.\n const sent = this.context.sendNavigate(href);\n if (sent) {\n window.history.pushState(null, \"\", href);\n this.currentHref = href;\n return;\n }\n // sendNavigate returned false \u2014 fall through to fetch as recovery.\n }\n // HTTP mode, WS not OPEN, sendNavigate returned false, or popstate:\n // fall through to normal fetch. pushState is handled downstream.\n }\n\n // Cancel any in-flight navigation fetch\n this.abortController?.abort();\n this.abortController = new AbortController();\n\n try {\n const response = await fetch(href, {\n credentials: \"include\",\n headers: { Accept: \"text/html\" },\n signal: this.abortController.signal,\n });\n\n if (!response.ok) {\n window.location.href = href;\n return;\n }\n\n const html = await response.text();\n\n // Push state BEFORE handling response so that cross-handler\n // navigation reconnects the WebSocket to the correct URL.\n // connect() derives the WebSocket path from window.location.\n if (pushState) {\n window.history.pushState(null, \"\", href);\n }\n\n this.currentHref = href;\n this.context.handleNavigationResponse(html);\n } catch (e: unknown) {\n // AbortError means a new navigation superseded this one \u2014 ignore\n if (e instanceof DOMException && e.name === \"AbortError\") return;\n window.location.href = href;\n }\n }\n}\n", "import type { Logger } from \"../utils/logger\";\n\nexport interface ObserverContext {\n getWrapperElement(): Element | null;\n send(message: any): void;\n}\n\n/**\n * Manages LiveTemplate observers such as infinite scroll and DOM mutations.\n */\nexport class ObserverManager {\n private infiniteScrollObserver: IntersectionObserver | null = null;\n private mutationObserver: MutationObserver | null = null;\n private observedSentinel: Element | null = null;\n private updatedListener: ((e: Event) => void) | null = null;\n private updatedListenerWrapper: Element | null = null;\n\n // Throttles infinite-scroll dispatches: one in-flight load_more at a time.\n // Without this, rapid observer re-fires stack concurrent actions and the\n // server's per-response diffs compose into duplicate rows on the client.\n //\n // Cleared precisely when the server confirms the load_more response has\n // been applied (via the `lvt:updated` event with `action === \"load_more\"`),\n // NOT on every DOM mutation \u2014 an unrelated mutation (e.g. a flash message\n // toggling, a highlight flashing) between the dispatch and the response\n // would otherwise clear the flag early and allow a concurrent send.\n private loadMorePending = false;\n // Safety net: if the server never responds with an lvt:updated event\n // for load_more (network error, server-side exception, server just\n // dropped the message), the flag above would stay true forever and\n // infinite scroll would be silently deadlocked until page reload.\n // This timeout releases the throttle after 30s so the next sentinel\n // intersection can re-trigger the load.\n private loadMoreTimeoutId: number | null = null;\n private static readonly LOAD_MORE_TIMEOUT_MS = 30000;\n\n constructor(\n private readonly context: ObserverContext,\n private readonly logger: Logger\n ) {}\n\n setupInfiniteScrollObserver(): void {\n const wrapperElement = this.context.getWrapperElement();\n if (!wrapperElement) return;\n\n // Attach the lvt:updated listener once per wrapper. The event fires\n // after every tree update carrying the dispatched action's name in\n // its detail; we use action === \"load_more\" as the precise signal\n // that the throttle can be lifted.\n this.ensureUpdatedListener(wrapperElement);\n\n const sentinel = document.getElementById(\"scroll-sentinel\");\n if (!sentinel) {\n // Sentinel removed (HasMore flipped false): release the old observer\n // AND clear any in-flight load_more throttle. Without releaseLoadMore\n // here, a HasMore flip-flop (server removes sentinel, then restores\n // it moments later without dispatching an lvt:updated action=load_more\n // in between) would leave loadMorePending=true with a 30s safety\n // timer armed \u2014 silently dropping the next intersection until the\n // timer fires.\n if (this.infiniteScrollObserver) {\n this.infiniteScrollObserver.disconnect();\n this.infiniteScrollObserver = null;\n this.observedSentinel = null;\n }\n this.releaseLoadMore();\n return;\n }\n\n // Reuse the existing observer when the sentinel node is the same \u2014\n // avoids allocating a fresh IntersectionObserver per DOM mutation.\n if (this.infiniteScrollObserver && this.observedSentinel === sentinel) {\n return;\n }\n\n if (this.infiniteScrollObserver) {\n this.infiniteScrollObserver.disconnect();\n }\n\n this.infiniteScrollObserver = new IntersectionObserver(\n (entries) => {\n if (!entries[0].isIntersecting) return;\n if (this.loadMorePending) {\n this.logger.debug(\"Sentinel visible but load_more already pending, skipping\");\n return;\n }\n this.loadMorePending = true;\n this.armLoadMoreTimeout();\n this.logger.debug(\"Sentinel visible, sending load_more action\");\n this.context.send({ action: \"load_more\" });\n },\n {\n rootMargin: \"200px\",\n }\n );\n\n this.infiniteScrollObserver.observe(sentinel);\n this.observedSentinel = sentinel;\n this.logger.debug(\"Observer set up successfully\");\n }\n\n private ensureUpdatedListener(wrapper: Element): void {\n if (this.updatedListener && this.updatedListenerWrapper === wrapper) return;\n // Detach any listener from the previous wrapper (e.g. after cross-\n // handler navigation swaps the wrapper element).\n if (this.updatedListener && this.updatedListenerWrapper) {\n this.updatedListenerWrapper.removeEventListener(\"lvt:updated\", this.updatedListener);\n }\n this.updatedListener = (e: Event) => {\n const detail = (e as CustomEvent).detail;\n if (detail?.action !== \"load_more\") return;\n this.releaseLoadMore();\n // Force a fresh IntersectionObserver so its immediate callback fires\n // with the post-mutation intersection state \u2014 lets the auto-advance\n // cascade continue if the sentinel is still visible after the new\n // rows are appended.\n this.observedSentinel = null;\n this.setupInfiniteScrollObserver();\n };\n wrapper.addEventListener(\"lvt:updated\", this.updatedListener);\n this.updatedListenerWrapper = wrapper;\n }\n\n private armLoadMoreTimeout(): void {\n this.clearLoadMoreTimeout();\n this.loadMoreTimeoutId = window.setTimeout(() => {\n this.logger.warn(\n `load_more response not received within ${ObserverManager.LOAD_MORE_TIMEOUT_MS}ms; releasing throttle`\n );\n this.loadMoreTimeoutId = null;\n this.loadMorePending = false;\n // Force the next intersection to re-trigger by rebuilding the\n // observer, since the sentinel may still be visible.\n this.observedSentinel = null;\n this.setupInfiniteScrollObserver();\n }, ObserverManager.LOAD_MORE_TIMEOUT_MS);\n }\n\n private releaseLoadMore(): void {\n this.loadMorePending = false;\n this.clearLoadMoreTimeout();\n }\n\n private clearLoadMoreTimeout(): void {\n if (this.loadMoreTimeoutId !== null) {\n clearTimeout(this.loadMoreTimeoutId);\n this.loadMoreTimeoutId = null;\n }\n }\n\n setupInfiniteScrollMutationObserver(): void {\n const wrapperElement = this.context.getWrapperElement();\n if (!wrapperElement) return;\n\n if (this.mutationObserver) {\n this.mutationObserver.disconnect();\n }\n\n // The mutation observer catches structural changes that replace the\n // sentinel DOM node (e.g. morphdom recreating it on a page-level\n // restructure). setupInfiniteScrollObserver's identity check makes\n // the common case \u2014 same sentinel, new mutation \u2014 a cheap no-op.\n this.mutationObserver = new MutationObserver(() => {\n this.setupInfiniteScrollObserver();\n });\n\n this.mutationObserver.observe(wrapperElement, {\n childList: true,\n subtree: true,\n });\n\n this.logger.debug(\"MutationObserver set up successfully\");\n }\n\n teardown(): void {\n if (this.infiniteScrollObserver) {\n this.infiniteScrollObserver.disconnect();\n this.infiniteScrollObserver = null;\n }\n if (this.mutationObserver) {\n this.mutationObserver.disconnect();\n this.mutationObserver = null;\n }\n if (this.updatedListener && this.updatedListenerWrapper) {\n this.updatedListenerWrapper.removeEventListener(\"lvt:updated\", this.updatedListener);\n }\n this.updatedListener = null;\n this.updatedListenerWrapper = null;\n this.observedSentinel = null;\n this.releaseLoadMore();\n }\n}\n", "/**\n * Handles showing and hiding the global LiveTemplate loading indicator.\n */\nexport class LoadingIndicator {\n private bar: HTMLElement | null = null;\n\n show(): void {\n if (this.bar) return;\n\n const bar = document.createElement(\"div\");\n bar.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: 3px;\n background: linear-gradient(90deg, #3b82f6 0%, #60a5fa 50%, #3b82f6 100%);\n background-size: 200% 100%;\n z-index: 9999;\n animation: lvt-loading-shimmer 1.5s ease-in-out infinite;\n `;\n\n if (!document.getElementById(\"lvt-loading-styles\")) {\n const style = document.createElement(\"style\");\n style.id = \"lvt-loading-styles\";\n style.textContent = `\n @keyframes lvt-loading-shimmer {\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n }\n `;\n document.head.appendChild(style);\n }\n\n document.body.insertBefore(bar, document.body.firstChild);\n this.bar = bar;\n }\n\n hide(): void {\n if (!this.bar) return;\n\n if (this.bar.parentNode) {\n this.bar.parentNode.removeChild(this.bar);\n }\n this.bar = null;\n }\n}\n", "/**\n * Enables and disables all form controls inside the LiveTemplate wrapper.\n */\nexport class FormDisabler {\n disable(wrapper: Element | null): void {\n if (!wrapper) return;\n\n const forms = wrapper.querySelectorAll(\"form\");\n forms.forEach((form) => {\n const inputs = form.querySelectorAll(\"input, textarea, select, button\");\n inputs.forEach((input) => {\n (input as HTMLInputElement).disabled = true;\n });\n });\n }\n\n enable(wrapper: Element | null): void {\n if (!wrapper) return;\n\n const forms = wrapper.querySelectorAll(\"form\");\n forms.forEach((form) => {\n const inputs = form.querySelectorAll(\"input, textarea, select, button\");\n inputs.forEach((input) => {\n (input as HTMLInputElement).disabled = false;\n });\n });\n }\n}\n", "/**\n * Polyfill for the HTML Invoker Commands API (command/commandfor).\n * Enables <button command=\"show-modal\" commandfor=\"dialog-id\"> to work\n * cross-browser by calling .showModal()/.close() on the target <dialog>.\n *\n * No-op when native support is detected.\n * Spec: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/command\n */\n\nlet installed = false;\n\nfunction handleClick(e: Event): void {\n const el = e.target;\n if (!el || !(el instanceof Element)) return;\n\n const button = el.closest(\"button[commandfor]\") as HTMLButtonElement | null;\n if (!button || button.disabled) return;\n\n const targetId = button.getAttribute(\"commandfor\");\n if (!targetId) return;\n\n const command = button.getAttribute(\"command\");\n const target = document.getElementById(targetId);\n if (!target || !(target instanceof HTMLDialogElement)) return;\n\n if (command === \"show-modal\" && !target.open) {\n target.showModal();\n } else if (command === \"close\" && target.open) {\n target.close();\n }\n}\n\nexport function setupInvokerPolyfill(): void {\n if (\"commandForElement\" in HTMLButtonElement.prototype) return;\n if (installed) return;\n\n installed = true;\n document.addEventListener(\"click\", handleClick);\n}\n\n/** @internal Reset polyfill state. Test-only \u2014 do not call from production code. */\nexport function teardownInvokerPolyfill(): void {\n if (installed) {\n document.removeEventListener(\"click\", handleClick);\n installed = false;\n }\n}\n", "import type { TreeNode, UpdateResult } from \"../types\";\nimport type { Logger } from \"../utils/logger\";\n\ninterface RangeStateEntry {\n items: any[];\n statics: any[];\n staticsMap?: Record<string, string[]>;\n}\n\n/**\n * Deep clone an object. Uses structuredClone if available (Node 17+, modern browsers),\n * falls back to JSON.parse/stringify for older environments.\n */\nfunction deepClone<T>(obj: T): T {\n if (typeof structuredClone === \"function\") {\n return structuredClone(obj);\n }\n return JSON.parse(JSON.stringify(obj));\n}\n\n/**\n * Checks if a node is a valid range structure.\n *\n * A \"range\" in LiveTemplate represents a {{range .Items}}...{{end}} construct.\n * It has:\n * - `d` (dynamics): Array of rendered items\n * - `s` (statics): Array of static HTML fragments between dynamic slots\n *\n * A \"non-range\" is any other tree node (e.g., an {{else}} clause with simple content).\n *\n * @param node - The tree node to check\n * @returns true if the node has both `d` and `s` arrays (valid range structure)\n */\nfunction isRangeNode(node: any): boolean {\n return (\n node != null &&\n typeof node === \"object\" &&\n Array.isArray(node.d) &&\n Array.isArray(node.s)\n );\n}\n\n/**\n * Checks if a node or any of its nested children contains a range structure.\n *\n * This is needed for detecting structure transitions like:\n * - {{if .ShowList}}<ul>{{range .Items}}...{{end}}</ul>{{else}}<p>Hidden</p>{{end}}\n *\n * When ShowList changes from true to false, the outer node doesn't have `d` directly,\n * but its child at position \"0\" does. We need to detect this nested range to know\n * that a full replacement is required instead of a merge.\n *\n * @param node - The tree node to check\n * @returns true if this node or any nested child has a range structure\n */\nfunction hasRangeAnywhere(node: any, depth = 0): boolean {\n // Depth limit to prevent potential stack overflow on deeply nested or malformed trees\n const MAX_DEPTH = 50;\n if (depth > MAX_DEPTH || node == null || typeof node !== \"object\" || Array.isArray(node)) {\n return false;\n }\n\n // Check if this node itself is a range\n if (isRangeNode(node)) {\n return true;\n }\n\n // Check numeric keys (dynamic positions) for nested ranges\n for (const key of Object.keys(node)) {\n if (/^\\d+$/.test(key)) {\n const child = node[key];\n if (child != null && typeof child === \"object\" && !Array.isArray(child)) {\n if (hasRangeAnywhere(child, depth + 1)) {\n return true;\n }\n }\n }\n }\n\n return false;\n}\n\n/**\n * Determines if a structure transition requires full replacement instead of merge.\n *\n * This handles cases where:\n * 1. Old structure has a range (directly or nested) but new structure doesn't\n * 2. New structure has statics (indicating a complete structure definition)\n * 3. Old structure has dynamics that new structure doesn't have\n *\n * @param existing - The existing tree node\n * @param update - The update tree node\n * @returns true if the update should fully replace existing instead of merging\n */\nfunction shouldFullReplace(existing: any, update: any): boolean {\n // If update doesn't have statics, it's a partial update, not a replacement\n if (!update.s || !Array.isArray(update.s)) {\n return false;\n }\n\n // Check for range\u2192non-range transition (including nested ranges)\n if (hasRangeAnywhere(existing) && !hasRangeAnywhere(update)) {\n return true;\n }\n\n // Check if existing has dynamics that update doesn't have\n for (const key of Object.keys(existing)) {\n if (/^\\d+$/.test(key) && !(key in update)) {\n // Existing has a dynamic position that update doesn't\n // If update has statics, this is a structure change\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Handles tree state management and HTML reconstruction logic for LiveTemplate.\n */\nexport class TreeRenderer {\n private treeState: TreeNode = {};\n private rangeState: Record<string, RangeStateEntry> = {};\n private rangeIdKeys: Record<string, string> = {};\n\n constructor(private readonly logger: Logger) {}\n\n applyUpdate(update: TreeNode): UpdateResult {\n let changed = false;\n\n for (const [key, value] of Object.entries(update)) {\n const isDifferentialOps =\n Array.isArray(value) &&\n value.length > 0 &&\n Array.isArray(value[0]) &&\n typeof value[0][0] === \"string\";\n\n if (isDifferentialOps) {\n // Check if there's an existing range structure to apply operations to\n const existing = this.treeState[key];\n const existingIsRange =\n existing &&\n typeof existing === \"object\" &&\n !Array.isArray(existing) &&\n Array.isArray(existing.d) &&\n Array.isArray(existing.s);\n\n if (existingIsRange) {\n // Apply differential operations to existing range structure\n this.treeState[key] = deepClone(existing);\n this.applyDifferentialOpsToRange(this.treeState[key], value, key);\n } else {\n // No existing range, store operations directly (will use rangeState later)\n this.treeState[key] = value;\n }\n changed = true;\n } else {\n const oldValue = this.treeState[key];\n const newValue =\n typeof value === \"object\" && value !== null && !Array.isArray(value)\n ? this.deepMergeTreeNodes(oldValue, value, key)\n : value;\n\n if (JSON.stringify(oldValue) !== JSON.stringify(newValue)) {\n this.treeState[key] = newValue;\n changed = true;\n }\n }\n }\n\n const html = this.reconstructFromTree(this.treeState, \"\");\n return { html, changed };\n }\n\n reset(): void {\n this.treeState = {};\n this.rangeState = {};\n this.rangeIdKeys = {};\n }\n\n getTreeState(): TreeNode {\n return { ...this.treeState };\n }\n\n getStaticStructure(): string[] | null {\n return this.treeState.s || null;\n }\n\n private deepMergeTreeNodes(\n existing: any,\n update: any,\n currentPath: string = \"\"\n ): any {\n if (\n typeof update !== \"object\" ||\n update === null ||\n Array.isArray(update)\n ) {\n return update;\n }\n\n if (\n typeof existing !== \"object\" ||\n existing === null ||\n Array.isArray(existing)\n ) {\n return update;\n }\n\n // Detect structure transitions that require full replacement instead of merge.\n // This handles:\n // 1. Direct range\u2192non-range: existing has d/s arrays, update doesn't\n // 2. Nested range\u2192non-range: existing has range in a child, update doesn't\n // 3. Structure changes: existing has dynamics that update doesn't have\n //\n // Without this check, old range data would be preserved and mixed with\n // new statics, causing wrong content or memory leaks.\n if (shouldFullReplace(existing, update)) {\n this.logger.debug(\n `[deepMerge] Structure transition at path ${currentPath}, replacing instead of merging`\n );\n return update;\n }\n\n const merged: any = { ...existing };\n\n for (const [key, value] of Object.entries(update)) {\n const fieldPath = currentPath ? `${currentPath}.${key}` : key;\n\n // Check if value is a differential operations array\n const isDifferentialOps =\n Array.isArray(value) &&\n value.length > 0 &&\n Array.isArray(value[0]) &&\n typeof value[0][0] === \"string\";\n\n // Check if existing value is a range structure\n const existingIsRange =\n merged[key] &&\n typeof merged[key] === \"object\" &&\n !Array.isArray(merged[key]) &&\n Array.isArray(merged[key].d) &&\n Array.isArray(merged[key].s);\n\n if (isDifferentialOps && existingIsRange) {\n // Deep clone the range structure before modifying to avoid mutating the original\n // (shallow copy {...existing} keeps shared references to nested objects)\n merged[key] = deepClone(merged[key]);\n // Apply differential operations to the cloned range\n this.logger.debug(\n `[deepMerge] Applying diff ops at path ${fieldPath}`,\n { ops: value, rangeItems: merged[key].d?.length }\n );\n this.applyDifferentialOpsToRange(merged[key], value, fieldPath);\n } else if (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n typeof merged[key] === \"object\" &&\n merged[key] !== null &&\n !Array.isArray(merged[key])\n ) {\n merged[key] = this.deepMergeTreeNodes(merged[key], value, fieldPath);\n } else {\n merged[key] = value;\n }\n }\n\n return merged;\n }\n\n /**\n * Applies differential operations to the provided range structure in-place.\n * The caller is responsible for passing the object to be mutated (typically a clone).\n * This is called when merging nested updates that contain range operations.\n */\n private applyDifferentialOpsToRange(\n rangeStructure: any,\n operations: any[],\n statePath: string\n ): void {\n // Validate rangeStructure before proceeding\n if (\n !rangeStructure ||\n typeof rangeStructure !== \"object\" ||\n !Array.isArray(rangeStructure.d) ||\n !Array.isArray(rangeStructure.s)\n ) {\n this.logger.error(\n `[applyDiffOpsToRange] Invalid rangeStructure at path ${statePath}`,\n { rangeStructure }\n );\n return;\n }\n\n const currentItems = rangeStructure.d;\n\n // Ensure rangeState is synchronized\n if (!this.rangeState[statePath]) {\n this.rangeState[statePath] = {\n items: currentItems,\n statics: rangeStructure.s,\n staticsMap: rangeStructure.sm,\n };\n }\n // Also check for idKey metadata\n if (\n rangeStructure.m &&\n typeof rangeStructure.m === \"object\" &&\n typeof rangeStructure.m.idKey === \"string\"\n ) {\n this.rangeIdKeys[statePath] = rangeStructure.m.idKey;\n }\n\n this.logger.debug(\n `[applyDiffOpsToRange] path=${statePath}, idKey=${this.rangeIdKeys[statePath]}, items=${currentItems.length}, ops=${operations.length}`\n );\n\n for (const operation of operations) {\n if (!Array.isArray(operation) || operation.length < 2) {\n continue;\n }\n\n const opType = operation[0];\n\n switch (opType) {\n case \"r\": {\n const key = operation[1];\n const removeIndex = this.findItemIndexByKey(\n currentItems,\n key,\n rangeStructure.s,\n statePath\n );\n this.logger.debug(\n `[applyDiffOpsToRange] Remove: key=${key}, index=${removeIndex}, total=${currentItems.length}`\n );\n if (removeIndex >= 0) {\n currentItems.splice(removeIndex, 1);\n this.logger.debug(`[applyDiffOpsToRange] After removal: ${currentItems.length} items`);\n } else {\n this.logger.debug(`[applyDiffOpsToRange] Remove failed: key ${key} not found`);\n }\n break;\n }\n case \"u\": {\n const updateIndex = this.findItemIndexByKey(\n currentItems,\n operation[1],\n rangeStructure.s,\n statePath\n );\n const changes = operation[2];\n if (updateIndex >= 0 && changes) {\n currentItems[updateIndex] = this.mergeRangeItem(\n currentItems[updateIndex],\n changes,\n statePath\n );\n }\n break;\n }\n case \"a\": {\n const itemsToAdd = Array.isArray(operation[1])\n ? operation[1]\n : [operation[1]];\n if (operation[2]) {\n rangeStructure.s = operation[2];\n }\n currentItems.push(...itemsToAdd);\n if (\n operation[3] &&\n typeof operation[3] === \"object\" &&\n operation[3].idKey\n ) {\n this.rangeIdKeys[statePath] = operation[3].idKey;\n }\n break;\n }\n case \"p\": {\n const itemsToPrepend = Array.isArray(operation[1])\n ? operation[1]\n : [operation[1]];\n if (operation[2]) {\n rangeStructure.s = operation[2];\n }\n currentItems.unshift(...itemsToPrepend);\n break;\n }\n case \"i\": {\n const targetIndex = this.findItemIndexByKey(\n currentItems,\n operation[1],\n rangeStructure.s,\n statePath\n );\n if (targetIndex >= 0) {\n const itemsToInsert = Array.isArray(operation[2])\n ? operation[2]\n : [operation[2]];\n currentItems.splice(targetIndex + 1, 0, ...itemsToInsert);\n }\n break;\n }\n case \"o\": {\n const newOrder = operation[1] as string[];\n const reorderedItems: any[] = [];\n const itemsByKey = new Map<string, any>();\n\n for (const item of currentItems) {\n const itemKey = this.getItemKey(item, rangeStructure.s, statePath);\n if (itemKey) {\n itemsByKey.set(itemKey, item);\n }\n }\n\n for (const orderedKey of newOrder) {\n const item = itemsByKey.get(orderedKey);\n if (item) {\n reorderedItems.push(item);\n }\n }\n\n currentItems.length = 0;\n currentItems.push(...reorderedItems);\n break;\n }\n default:\n break;\n }\n }\n\n // Update rangeState to reflect the changes\n this.rangeState[statePath] = {\n items: currentItems,\n statics: rangeStructure.s,\n staticsMap: rangeStructure.sm,\n };\n }\n\n private reconstructFromTree(node: TreeNode, statePath: string): string {\n if (node.s && Array.isArray(node.s)) {\n let html = \"\";\n\n for (let i = 0; i < node.s.length; i++) {\n const staticSegment = node.s[i];\n html += staticSegment;\n\n if (i < node.s.length - 1) {\n const dynamicKey = i.toString();\n if (node[dynamicKey] !== undefined) {\n const newStatePath = statePath\n ? `${statePath}.${dynamicKey}`\n : dynamicKey;\n html += this.renderValue(\n node[dynamicKey],\n dynamicKey,\n newStatePath\n );\n }\n }\n }\n\n html = html.replace(/<root>/g, \"\").replace(/<\\/root>/g, \"\");\n return html;\n }\n\n return this.renderValue(node, \"\", statePath);\n }\n\n private renderValue(\n value: any,\n fieldKey?: string,\n statePath?: string\n ): string {\n if (value === null || value === undefined) {\n return \"\";\n }\n\n if (\n typeof value === \"string\" &&\n value.startsWith(\"{{\") &&\n value.endsWith(\"}}\")\n ) {\n return \"\";\n }\n\n if (typeof value === \"object\" && !Array.isArray(value)) {\n if (\n value.d &&\n Array.isArray(value.d) &&\n value.s &&\n Array.isArray(value.s)\n ) {\n const stateKey = statePath || fieldKey || \"\";\n if (stateKey) {\n this.rangeState[stateKey] = {\n items: value.d,\n statics: value.s,\n staticsMap: value.sm,\n };\n if (\n value.m &&\n typeof value.m === \"object\" &&\n typeof value.m.idKey === \"string\"\n ) {\n this.rangeIdKeys[stateKey] = value.m.idKey;\n }\n }\n return this.renderRangeStructure(value, fieldKey, statePath);\n }\n\n if (\"s\" in value && Array.isArray((value as TreeNode).s)) {\n return this.reconstructFromTree(value as TreeNode, statePath || \"\");\n }\n\n // Handle objects with only numeric keys (dynamics without statics)\n // This occurs when server sends partial updates for nested TreeNodes\n const keys = Object.keys(value);\n const numericKeys = keys.filter((k) => /^\\d+$/.test(k)).sort((a, b) => parseInt(a) - parseInt(b));\n if (numericKeys.length > 0 && numericKeys.length === keys.length) {\n // All keys are numeric - render each dynamic value in order\n return numericKeys\n .map((k) => {\n const itemStatePath = statePath ? `${statePath}.${k}` : k;\n return this.renderValue((value as Record<string, unknown>)[k], k, itemStatePath);\n })\n .join(\"\");\n }\n }\n\n if (Array.isArray(value)) {\n if (\n value.length > 0 &&\n Array.isArray(value[0]) &&\n typeof value[0][0] === \"string\"\n ) {\n return this.applyDifferentialOperations(value, statePath);\n }\n\n return value\n .map((item, idx) => {\n const itemKey = idx.toString();\n const itemStatePath = statePath ? `${statePath}.${itemKey}` : itemKey;\n if (typeof item === \"object\" && item && (item as TreeNode).s) {\n return this.reconstructFromTree(item as TreeNode, itemStatePath);\n }\n return this.renderValue(item, itemKey, itemStatePath);\n })\n .join(\"\");\n }\n\n if (typeof value === \"object\") {\n // Plain data objects (without tree structure) are state values that shouldn't be rendered.\n // This happens when state contains objects like EditingItem that are used by server-side\n // templates but aren't meant to be rendered directly in the DOM.\n // Skip them silently instead of converting to \"[object Object]\".\n this.logger.debug(\n \"Skipping plain object value (not a tree node) - this is normal for state-only data\"\n );\n return \"\";\n }\n\n return String(value);\n }\n\n private renderRangeStructure(\n rangeNode: any,\n fieldKey?: string,\n statePath?: string\n ): string {\n const { d: dynamics, s: statics, sm: staticsMap } = rangeNode;\n\n if (!dynamics || !Array.isArray(dynamics)) {\n return \"\";\n }\n\n if (dynamics.length === 0) {\n if (rangeNode[\"else\"]) {\n const elseKey = \"else\";\n const elseStatePath = statePath ? `${statePath}.else` : \"else\";\n return this.renderValue(rangeNode[\"else\"], elseKey, elseStatePath);\n }\n return \"\";\n }\n\n // Check if we have per-item statics via StaticsMap\n const hasStaticsMap = staticsMap && typeof staticsMap === \"object\";\n\n if (statics && Array.isArray(statics)) {\n return dynamics\n .map((item: any, itemIdx: number) => {\n // Get per-item statics from StaticsMap if available, otherwise use shared statics\n let itemStatics = statics;\n if (hasStaticsMap && item._sk && staticsMap[item._sk]) {\n itemStatics = staticsMap[item._sk];\n }\n\n let html = \"\";\n\n for (let i = 0; i < itemStatics.length; i++) {\n html += itemStatics[i];\n\n if (i < itemStatics.length - 1) {\n const localKey = i.toString();\n if (item[localKey] !== undefined) {\n const itemStatePath = statePath\n ? `${statePath}.${itemIdx}.${localKey}`\n : `${itemIdx}.${localKey}`;\n html += this.renderValue(\n item[localKey],\n localKey,\n itemStatePath\n );\n }\n }\n }\n\n return html;\n })\n .join(\"\");\n }\n\n return dynamics\n .map((item: any, idx: number) => {\n const itemKey = idx.toString();\n const itemStatePath = statePath ? `${statePath}.${itemKey}` : itemKey;\n return this.renderValue(item, itemKey, itemStatePath);\n })\n .join(\"\");\n }\n\n private applyDifferentialOperations(\n operations: any[],\n statePath?: string\n ): string {\n if (!statePath || !this.rangeState[statePath]) {\n return \"\";\n }\n\n const rangeData = this.rangeState[statePath];\n const currentItems = [...rangeData.items];\n const statics = rangeData.statics;\n\n for (const operation of operations) {\n if (!Array.isArray(operation) || operation.length < 2) {\n continue;\n }\n\n const opType = operation[0];\n\n switch (opType) {\n case \"r\": {\n const removeIndex = this.findItemIndexByKey(\n currentItems,\n operation[1],\n statics,\n statePath\n );\n if (removeIndex >= 0) {\n currentItems.splice(removeIndex, 1);\n }\n break;\n }\n case \"u\": {\n const updateIndex = this.findItemIndexByKey(\n currentItems,\n operation[1],\n statics,\n statePath\n );\n const changes = operation[2];\n if (updateIndex >= 0 && changes) {\n currentItems[updateIndex] = this.mergeRangeItem(\n currentItems[updateIndex],\n changes,\n statePath || \"\"\n );\n }\n break;\n }\n case \"a\": {\n this.addItemsToRange(\n currentItems,\n operation[1],\n operation[2],\n rangeData,\n false\n );\n if (\n operation[3] &&\n typeof operation[3] === \"object\" &&\n operation[3].idKey\n ) {\n this.rangeIdKeys[statePath || \"\"] = operation[3].idKey;\n }\n break;\n }\n case \"p\": {\n this.addItemsToRange(\n currentItems,\n operation[1],\n operation[2],\n rangeData,\n true\n );\n break;\n }\n case \"i\": {\n const targetIndex = this.findItemIndexByKey(\n currentItems,\n operation[1],\n statics,\n statePath\n );\n if (targetIndex >= 0) {\n const itemsToInsert = Array.isArray(operation[2])\n ? operation[2]\n : [operation[2]];\n currentItems.splice(targetIndex + 1, 0, ...itemsToInsert);\n }\n break;\n }\n case \"o\": {\n const newOrder = operation[1] as string[];\n const reorderedItems: any[] = [];\n const itemsByKey = new Map<string, any>();\n\n for (const item of currentItems) {\n const itemKey = this.getItemKey(item, statics, statePath);\n if (itemKey) {\n itemsByKey.set(itemKey, item);\n }\n }\n\n for (const orderedKey of newOrder) {\n const item = itemsByKey.get(orderedKey);\n if (item) {\n reorderedItems.push(item);\n }\n }\n\n currentItems.length = 0;\n currentItems.push(...reorderedItems);\n break;\n }\n default:\n break;\n }\n }\n\n this.rangeState[statePath] = {\n items: currentItems,\n statics: rangeData.statics,\n staticsMap: rangeData.staticsMap,\n };\n\n this.treeState[statePath] = {\n d: currentItems,\n s: rangeData.statics,\n sm: rangeData.staticsMap,\n };\n\n const rangeStructure = this.getCurrentRangeStructure(statePath);\n if (rangeStructure && rangeStructure.s) {\n return this.renderItemsWithStatics(\n currentItems,\n rangeStructure.s,\n rangeStructure.sm,\n statePath\n );\n }\n\n return currentItems.map((item) => this.renderValue(item)).join(\"\");\n }\n\n private getCurrentRangeStructure(stateKey: string): any {\n if (this.rangeState[stateKey]) {\n return {\n d: this.rangeState[stateKey].items,\n s: this.rangeState[stateKey].statics,\n sm: this.rangeState[stateKey].staticsMap,\n };\n }\n\n const fieldValue = this.treeState[stateKey];\n if (\n fieldValue &&\n typeof fieldValue === \"object\" &&\n (fieldValue as TreeNode).s\n ) {\n return fieldValue;\n }\n\n return null;\n }\n\n private renderItemsWithStatics(\n items: any[],\n statics: string[],\n staticsMap?: Record<string, string[]>,\n statePath?: string\n ): string {\n const result = items\n .map((item: any, itemIdx: number) => {\n // Get per-item statics from StaticsMap if available, otherwise use shared statics\n let itemStatics = statics;\n if (\n staticsMap &&\n typeof staticsMap === \"object\" &&\n item._sk &&\n staticsMap[item._sk]\n ) {\n itemStatics = staticsMap[item._sk];\n }\n\n let html = \"\";\n\n for (let i = 0; i < itemStatics.length; i++) {\n html += itemStatics[i];\n\n if (i < itemStatics.length - 1) {\n const fieldKey = i.toString();\n if (item[fieldKey] !== undefined) {\n const itemStatePath = statePath\n ? `${statePath}.${itemIdx}.${fieldKey}`\n : `${itemIdx}.${fieldKey}`;\n html += this.renderValue(item[fieldKey], fieldKey, itemStatePath);\n }\n }\n }\n\n return html;\n })\n .join(\"\");\n\n if (this.logger.isDebugEnabled()) {\n this.logger.debug(\"[renderItemsWithStatics] statics:\", statics);\n this.logger.debug(\"[renderItemsWithStatics] items count:\", items.length);\n this.logger.debug(\n \"[renderItemsWithStatics] result snippet:\",\n result.substring(0, 200)\n );\n }\n\n return result;\n }\n\n private addItemsToRange(\n currentItems: any[],\n items: any,\n statics: any[] | undefined,\n rangeData: RangeStateEntry,\n prepend: boolean\n ): void {\n if (statics) {\n rangeData.statics = statics;\n }\n\n if (!items) return;\n\n const itemsArray = Array.isArray(items) ? items : [items];\n if (prepend) {\n currentItems.unshift(...itemsArray);\n } else {\n currentItems.push(...itemsArray);\n }\n }\n\n private getItemKey(\n item: any,\n statics: any[],\n statePath?: string\n ): string | null {\n // First check for auto-generated _k field (always takes priority)\n if (item._k && typeof item._k === \"string\") {\n return item._k;\n }\n\n if (!statePath || !this.rangeIdKeys[statePath]) {\n return null;\n }\n\n const keyPosStr = this.rangeIdKeys[statePath];\n return item[keyPosStr] || null;\n }\n\n private findItemIndexByKey(\n items: any[],\n key: string,\n statics: any[],\n statePath?: string\n ): number {\n return items.findIndex(\n (item: any) => this.getItemKey(item, statics, statePath) === key\n );\n }\n\n /**\n * Merges changes into a range item using deep merge to preserve statics.\n * When the server sends partial updates like {\"5\": {\"0\": \"new text\"}},\n * we need to merge this into the existing item's field 5, not replace it.\n * Shallow spread would lose the statics ({\"s\": [...]}) that the client has cached.\n */\n private mergeRangeItem(item: any, changes: any, statePath: string): any {\n return this.deepMergeTreeNodes(item, changes, `${statePath}.item`);\n }\n}\n", "import type { ResponseMetadata } from \"../types\";\n\n/**\n * Tracks form submission lifecycle for LiveTemplate actions.\n */\nexport class FormLifecycleManager {\n private activeForm: HTMLFormElement | null = null;\n private activeButton: HTMLButtonElement | null = null;\n private originalButtonText: string | null = null;\n\n constructor() {}\n\n setActiveSubmission(\n form: HTMLFormElement | null,\n button: HTMLButtonElement | null,\n originalButtonText: string | null\n ): void {\n this.activeForm = form;\n this.activeButton = button;\n this.originalButtonText = originalButtonText;\n\n // Auto aria-busy + fieldset disabled for loading states\n if (form) {\n form.setAttribute(\"aria-busy\", \"true\");\n const fieldset = form.querySelector(\"fieldset\");\n if (fieldset) {\n fieldset.disabled = true;\n }\n }\n }\n\n handleResponse(meta: ResponseMetadata): void {\n if (this.activeForm) {\n this.activeForm.dispatchEvent(\n new CustomEvent(\"lvt:done\", { detail: meta })\n );\n }\n\n if (meta.success) {\n this.handleSuccess(meta);\n } else {\n this.handleError(meta);\n }\n\n this.restoreFormState();\n }\n\n reset(): void {\n this.restoreFormState();\n }\n\n private handleSuccess(meta: ResponseMetadata): void {\n if (!this.activeForm) {\n return;\n }\n\n this.activeForm.dispatchEvent(\n new CustomEvent(\"lvt:success\", { detail: meta })\n );\n\n // Close parent <dialog> using native API instead of ModalManager\n const dialogParent = this.activeForm.closest(\"dialog\");\n if (dialogParent && dialogParent.open) {\n dialogParent.close();\n }\n\n if (!this.activeForm.hasAttribute(\"lvt-form:preserve\")) {\n this.activeForm.reset();\n }\n }\n\n private handleError(meta: ResponseMetadata): void {\n if (!this.activeForm) {\n return;\n }\n\n this.activeForm.dispatchEvent(\n new CustomEvent(\"lvt:error\", { detail: meta })\n );\n }\n\n private restoreFormState(): void {\n if (this.activeForm) {\n this.activeForm.removeAttribute(\"aria-busy\");\n const fieldset = this.activeForm.querySelector(\"fieldset\");\n if (fieldset) {\n fieldset.disabled = false;\n }\n }\n\n if (this.activeButton && this.originalButtonText !== null) {\n this.activeButton.disabled = false;\n this.activeButton.textContent = this.originalButtonText;\n }\n\n this.activeForm = null;\n this.activeButton = null;\n this.originalButtonText = null;\n }\n}\n", "import { debounce } from \"../utils/rate-limit\";\nimport { DEFAULT_CHANGE_DEBOUNCE_MS } from \"../constants\";\nimport type { TreeNode } from \"../types\";\nimport type { Logger } from \"../utils/logger\";\n\nexport type BindingType = \"value\" | \"content\" | \"attribute\";\n\nexport interface ChangeMessage {\n action: \"change\";\n data: Record<string, string | boolean>;\n}\n\nexport interface ChangeAutoWirerContext {\n getWrapperElement(): Element | null;\n send(message: ChangeMessage): void;\n}\n\n/**\n * Analyzes template statics to detect form fields with dynamic values,\n * then auto-wires debounced input event listeners when the server\n * declares \"change\" capability.\n */\nexport class ChangeAutoWirer {\n private boundFields: Map<string, BindingType> = new Map();\n private enabled: boolean = false;\n private wiredElements: Set<Element> = new Set();\n private elementCleanups: Map<Element, () => void> = new Map();\n\n constructor(\n private readonly context: ChangeAutoWirerContext,\n private readonly logger: Logger\n ) {}\n\n setCapabilities(capabilities: string[]): void {\n this.enabled = capabilities.includes(\"change\");\n this.logger.debug(\n \"Capabilities received, change auto-wiring:\",\n this.enabled ? \"enabled\" : \"disabled\"\n );\n }\n\n analyzeStatics(treeState: TreeNode): void {\n const prevSize = this.boundFields.size;\n this.walkTree(treeState);\n if (this.boundFields.size > prevSize) {\n this.logger.debug(\n `Detected ${this.boundFields.size} bound field(s):`,\n Array.from(this.boundFields.keys())\n );\n }\n }\n\n wireElements(): void {\n if (!this.enabled) return;\n\n const wrapper = this.context.getWrapperElement();\n if (!wrapper) return;\n\n // Evict stale entries for elements removed by morphdom so they can be re-wired\n for (const el of this.elementCleanups.keys()) {\n if (!el.isConnected) {\n this.elementCleanups.get(el)!();\n this.elementCleanups.delete(el);\n this.wiredElements.delete(el);\n }\n }\n\n for (const [fieldName, bindingType] of this.boundFields) {\n const escapedName = this.escapeCSSSelector(fieldName);\n const elements = wrapper.querySelectorAll(`[name=\"${escapedName}\"]`);\n\n for (const el of elements) {\n if (this.wiredElements.has(el)) continue;\n if (el.hasAttribute(\"lvt-input\") || el.hasAttribute(\"lvt-change\"))\n continue;\n\n const parentForm = el.closest(\"form\");\n if (parentForm) {\n if (parentForm.hasAttribute(\"lvt-change\")) continue;\n if (parentForm.hasAttribute(\"lvt-form:no-intercept\")) continue;\n }\n\n if (el instanceof HTMLInputElement) {\n if (\n el.type === \"hidden\" ||\n el.type === \"submit\" ||\n el.type === \"button\"\n )\n continue;\n }\n if (el instanceof HTMLButtonElement) continue;\n\n this.attachListener(el as HTMLElement, fieldName, bindingType);\n this.wiredElements.add(el);\n }\n }\n\n // Auto-wire all named selects when Change() is enabled.\n // Select bindings live on child <option> tags ({{if eq ...}}selected{{end}}),\n // so they aren't detected by static analysis. A named select always submits a value.\n const selects = wrapper.querySelectorAll(\"select[name]\");\n for (const el of selects) {\n if (this.wiredElements.has(el)) continue;\n if (el.hasAttribute(\"lvt-input\") || el.hasAttribute(\"lvt-change\"))\n continue;\n\n const parentForm = el.closest(\"form\");\n if (parentForm) {\n if (parentForm.hasAttribute(\"lvt-change\")) continue;\n if (parentForm.hasAttribute(\"lvt-form:no-intercept\")) continue;\n }\n\n const name = el.getAttribute(\"name\");\n if (!name) continue;\n this.attachListener(el as HTMLElement, name, \"value\");\n this.wiredElements.add(el);\n }\n }\n\n teardown(): void {\n for (const cleanup of this.elementCleanups.values()) {\n cleanup();\n }\n this.elementCleanups.clear();\n this.wiredElements.clear();\n this.boundFields.clear();\n this.enabled = false;\n }\n\n getBoundFields(): ReadonlyMap<string, BindingType> {\n return this.boundFields;\n }\n\n isEnabled(): boolean {\n return this.enabled;\n }\n\n private walkTree(node: any): void {\n if (!node || typeof node !== \"object\" || Array.isArray(node)) return;\n\n if (node.s && Array.isArray(node.s)) {\n this.analyzeStaticsArray(node.s);\n }\n\n for (const key of Object.keys(node)) {\n if (/^\\d+$/.test(key)) {\n const child = node[key];\n if (child && typeof child === \"object\" && !Array.isArray(child)) {\n this.walkTree(child);\n }\n }\n }\n\n if (node.d && Array.isArray(node.d)) {\n for (const item of node.d) {\n if (item && typeof item === \"object\" && !Array.isArray(item)) {\n this.walkTree(item);\n }\n }\n }\n }\n\n private analyzeStaticsArray(statics: string[]): void {\n for (let i = 0; i < statics.length - 1; i++) {\n const left = statics[i];\n const right = statics[i + 1];\n\n const binding = this.detectBinding(left, right);\n if (binding && !this.boundFields.has(binding.fieldName)) {\n this.boundFields.set(binding.fieldName, binding.bindingType);\n }\n }\n }\n\n private detectBinding(\n left: string,\n right: string\n ): { fieldName: string; bindingType: BindingType } | null {\n let fieldName = this.detectValueBinding(left);\n if (fieldName) {\n return { fieldName, bindingType: \"value\" };\n }\n\n fieldName = this.detectTextareaBinding(left, right);\n if (fieldName) {\n return { fieldName, bindingType: \"content\" };\n }\n\n fieldName = this.detectAttributeBinding(left, right);\n if (fieldName) {\n return { fieldName, bindingType: \"attribute\" };\n }\n\n return null;\n }\n\n private escapeCSSSelector(value: string): string {\n if (typeof CSS !== \"undefined\" && typeof CSS.escape === \"function\") {\n return CSS.escape(value);\n }\n // Fallback for jsdom test environment only. Browsers always have CSS.escape.\n // This handles the common cases; field names with brackets/dots would need\n // CSS.escape which is not available in jsdom.\n return value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n }\n\n /**\n * Extract the unclosed tag fragment at the end of a static string.\n * Returns null if the string does not end inside an open tag.\n */\n private extractUnclosedTag(text: string): string | null {\n const lastOpen = text.lastIndexOf(\"<\");\n const lastClose = text.lastIndexOf(\">\");\n if (lastOpen === -1 || lastOpen <= lastClose) return null;\n return text.substring(lastOpen);\n }\n\n private extractNameFromTag(partialTag: string): string | null {\n const match = partialTag.match(/\\sname=\"([^\"]+)\"/);\n return match ? match[1] : null;\n }\n\n /** Detects: <input name=\"X\" ... value=\"{{.X}}\"> */\n private detectValueBinding(left: string): string | null {\n if (!left.endsWith('value=\"')) return null;\n const partialTag = this.extractUnclosedTag(left);\n if (!partialTag) return null;\n return this.extractNameFromTag(partialTag);\n }\n\n /** Detects: <textarea name=\"X\">{{.X}}</textarea> */\n private detectTextareaBinding(left: string, right: string): string | null {\n if (!right.startsWith(\"</textarea\")) return null;\n const tagMatch = left.match(/<textarea[^>]*\\sname=\"([^\"]+)\"[^>]*>$/);\n return tagMatch ? tagMatch[1] : null;\n }\n\n /** Detects: <input type=\"checkbox\" name=\"X\" {{if .X}}checked{{end}}> */\n private detectAttributeBinding(left: string, right: string): string | null {\n if (!left.endsWith(\" \")) return null;\n if (!/^[a-zA-Z>/\\s]/.test(right)) return null;\n\n const partialTag = this.extractUnclosedTag(left);\n if (!partialTag) return null;\n\n // Only checkbox/radio inputs use attribute bindings (checked),\n // since attachListener reads .checked for this binding type\n if (!/^<input\\s/i.test(partialTag)) return null;\n const typeMatch = partialTag.match(/\\stype=\"([^\"]+)\"/i);\n if (!typeMatch) return null;\n const inputType = typeMatch[1].toLowerCase();\n if (inputType !== \"checkbox\" && inputType !== \"radio\") return null;\n\n return this.extractNameFromTag(partialTag);\n }\n\n private attachListener(\n element: HTMLElement,\n fieldName: string,\n bindingType: BindingType\n ): void {\n const customDebounce = element.getAttribute(\"lvt-debounce\");\n const parsed = customDebounce ? parseInt(customDebounce, 10) : NaN;\n const wait =\n Number.isNaN(parsed) || parsed < 0 ? DEFAULT_CHANGE_DEBOUNCE_MS : parsed;\n\n const sendChange = () => {\n if (!this.enabled) return;\n\n const value: string | boolean =\n bindingType === \"attribute\" &&\n (element as HTMLInputElement).type === \"checkbox\"\n ? (element as HTMLInputElement).checked\n : (element as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement)\n .value;\n\n this.context.send({\n action: \"change\",\n data: { [fieldName]: value },\n });\n };\n\n const debouncedSend = debounce(sendChange, wait);\n\n // Discrete events (checkbox toggle, select change) use 'change';\n // continuous typing uses 'input'\n const eventType =\n bindingType === \"attribute\" || element instanceof HTMLSelectElement\n ? \"change\"\n : \"input\";\n\n element.addEventListener(eventType, debouncedSend);\n\n this.elementCleanups.set(element, () => {\n element.removeEventListener(eventType, debouncedSend);\n });\n\n this.logger.debug(\n `Auto-wired ${eventType} listener on [name=\"${fieldName}\"] (debounce: ${wait}ms)`\n );\n }\n}\n", "import type { LiveTemplateClientOptions, UpdateResponse } from \"../types\";\nimport type { Logger } from \"../utils/logger\";\n\nexport interface WebSocketTransportOptions {\n url: string;\n autoReconnect?: boolean;\n reconnectDelay?: number;\n maxReconnectDelay?: number; // Maximum delay between reconnect attempts (default: 16000ms)\n maxReconnectAttempts?: number; // Maximum number of reconnect attempts (default: 10, 0 = unlimited)\n onOpen?: (socket: WebSocket) => void;\n onMessage?: (event: MessageEvent<string>) => void;\n onClose?: (event: CloseEvent) => void;\n onReconnectAttempt?: (attempt: number, delay: number) => void;\n onReconnectFailed?: () => void; // Called when max reconnect attempts reached\n onError?: (event: Event) => void;\n}\n\n/**\n * Lightweight wrapper around browser WebSocket with optional auto-reconnect support.\n * Implements exponential backoff with jitter to prevent thundering herd.\n */\nexport class WebSocketTransport {\n private socket: WebSocket | null = null;\n private reconnectTimer: number | null = null;\n private manuallyClosed = false;\n private reconnectAttempts = 0;\n\n constructor(private readonly options: WebSocketTransportOptions) {}\n\n connect(): void {\n this.manuallyClosed = false;\n this.clearReconnectTimer();\n\n this.socket = new WebSocket(this.options.url);\n const socket = this.socket;\n\n socket.onopen = () => {\n // Reset reconnect attempts on successful connection\n this.reconnectAttempts = 0;\n this.options.onOpen?.(socket);\n };\n\n socket.onmessage = (event: MessageEvent<string>) => {\n this.options.onMessage?.(event);\n };\n\n socket.onclose = (event: CloseEvent) => {\n this.options.onClose?.(event);\n if (!this.manuallyClosed && this.options.autoReconnect) {\n this.scheduleReconnect();\n }\n };\n\n socket.onerror = (event: Event) => {\n this.options.onError?.(event);\n };\n }\n\n send(data: string): void {\n if (this.socket && this.socket.readyState === 1) { // WebSocket.OPEN = 1\n this.socket.send(data);\n }\n }\n\n disconnect(): void {\n this.manuallyClosed = true;\n this.clearReconnectTimer();\n if (this.socket) {\n this.socket.close();\n this.socket = null;\n }\n }\n\n getSocket(): WebSocket | null {\n return this.socket;\n }\n\n private scheduleReconnect(): void {\n this.clearReconnectTimer();\n\n // Check if max reconnect attempts reached\n const maxAttempts = this.options.maxReconnectAttempts ?? 10;\n if (maxAttempts > 0 && this.reconnectAttempts >= maxAttempts) {\n this.options.onReconnectFailed?.();\n return;\n }\n\n this.reconnectAttempts++;\n\n // Calculate exponential backoff: baseDelay * 2^attempt\n const baseDelay = this.options.reconnectDelay ?? 1000;\n const maxDelay = this.options.maxReconnectDelay ?? 16000;\n const exponentialDelay = baseDelay * Math.pow(2, this.reconnectAttempts - 1);\n\n // Add jitter: random value between 0 and 1000ms to prevent thundering herd\n const jitter = Math.random() * 1000;\n\n // Calculate final delay with maximum cap\n const delay = Math.min(exponentialDelay + jitter, maxDelay);\n\n this.reconnectTimer = window.setTimeout(() => {\n this.options.onReconnectAttempt?.(this.reconnectAttempts, delay);\n this.connect();\n }, delay);\n }\n\n private clearReconnectTimer(): void {\n if (this.reconnectTimer !== null) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n}\n\nexport interface WebSocketManagerConfig {\n options: LiveTemplateClientOptions;\n onConnected: () => void;\n onDisconnected: () => void;\n onMessage: (response: UpdateResponse, event: MessageEvent<string>) => void;\n onReconnectAttempt?: (attempt: number, delay: number) => void;\n onReconnectFailed?: () => void;\n onError?: (event: Event) => void;\n logger: Logger;\n}\n\nexport interface WebSocketConnectResult {\n usingWebSocket: boolean;\n initialState?: UpdateResponse | null;\n}\n\nexport class WebSocketManager {\n private transport: WebSocketTransport | null = null;\n // Optional override for liveUrl, set by the client on cross-handler\n // navigation. When set, takes precedence over options.liveUrl. This\n // avoids mutating the caller-provided options object.\n private liveUrlOverride: string | null = null;\n\n constructor(private readonly config: WebSocketManagerConfig) {}\n\n /**\n * Update the live URL used for WebSocket reconnection. Called by the\n * client on cross-handler navigation so the next connect() uses the\n * new page path without mutating the shared options object.\n */\n setLiveUrl(liveUrl: string): void {\n this.liveUrlOverride = liveUrl;\n }\n\n async connect(): Promise<WebSocketConnectResult> {\n const liveUrl = this.getLiveUrl();\n\n const wsAvailable = await checkWebSocketAvailability(\n liveUrl,\n this.config.logger\n );\n if (!wsAvailable) {\n const initialState = await fetchInitialState(liveUrl, this.config.logger);\n return { usingWebSocket: false, initialState };\n }\n\n // Await onopen before resolving, so downstream setup runs with a ready\n // transport. Without this, observer callbacks during CONNECTING fall\n // back to HTTP, which hits a separate server state path than the WS\n // event loop \u2014 producing stale/desynced responses.\n //\n // Three settle paths: onOpen (success), onClose/onError (immediate\n // failure), and a 10s timeout (silent network drop \u2014 TCP may never\n // surface a close/error event through a misbehaving middlebox).\n let resolveOpen!: (value: WebSocketConnectResult) => void;\n let rejectOpen!: (reason?: unknown) => void;\n const openPromise = new Promise<WebSocketConnectResult>((resolve, reject) => {\n resolveOpen = resolve;\n rejectOpen = reject;\n });\n let settled = false;\n // Tracks whether onOpen ever fired. Gates onDisconnected so we don't\n // fire a spurious \"disconnected\" notification on the HTTP fallback\n // path where the socket closed before it ever opened (either\n // naturally via onClose/onError, or because the catch block\n // explicitly disconnects the transport after the 10s timeout).\n let hasConnected = false;\n // Declared before settleOpen to avoid a temporal dead zone on the\n // closure reference \u2014 settleOpen is called from onOpen/onClose/onError\n // (all async, so safe today), but declaring the binding up front lets\n // any future synchronous call path still work without a ReferenceError.\n let openTimeoutId: ReturnType<typeof setTimeout> | null = null;\n const settleOpen = (err?: Error): void => {\n if (settled) return;\n settled = true;\n if (openTimeoutId !== null) {\n clearTimeout(openTimeoutId);\n openTimeoutId = null;\n }\n if (err) rejectOpen(err);\n else resolveOpen({ usingWebSocket: true });\n };\n openTimeoutId = setTimeout(() => {\n settleOpen(new Error(\"WebSocket open timed out after 10s\"));\n }, 10000);\n\n this.transport = new WebSocketTransport({\n url: this.getWebSocketUrl(),\n autoReconnect: this.config.options.autoReconnect,\n reconnectDelay: this.config.options.reconnectDelay,\n maxReconnectDelay: 16000, // 16 seconds maximum\n maxReconnectAttempts: 10, // 10 attempts before giving up\n onOpen: () => {\n hasConnected = true;\n this.config.onConnected();\n settleOpen();\n },\n onMessage: (event) => {\n try {\n const payload: UpdateResponse = JSON.parse(event.data);\n this.config.onMessage(payload, event);\n } catch (error) {\n this.config.logger.error(\"Failed to parse WebSocket message:\", error);\n }\n },\n onClose: () => {\n // Branch on whether we'd actually connected:\n // - Success path (onOpen fired): notify client of disconnection\n // via onDisconnected(). Do NOT call settleOpen \u2014 it's already\n // resolved, and we'd needlessly construct an Error object\n // that shows up in any log/trace capturing rejection reasons.\n // - Failure path (close before open): reject the openPromise\n // with a descriptive Error. onDisconnected is NOT fired\n // because we were never \"connected\" from the client's POV.\n if (hasConnected) {\n this.config.onDisconnected();\n } else {\n settleOpen(new Error(\"WebSocket closed before it opened\"));\n }\n },\n onReconnectAttempt: (attempt, delay) => {\n this.config.onReconnectAttempt?.(attempt, delay);\n },\n onReconnectFailed: () => {\n this.config.onReconnectFailed?.();\n },\n onError: (event) => {\n this.config.onError?.(event);\n // If we're already connected, onDisconnected is fired via the\n // subsequent onClose \u2014 per WHATWG WebSocket spec, onclose always\n // fires after onerror when the connection is lost post-open.\n // We rely on that invariant here rather than double-firing\n // onDisconnected (which would require tracking a separate\n // \"already disconnected\" flag).\n //\n // If we're NOT yet connected, this rejects openPromise so the\n // catch block falls back to HTTP. settleOpen is a no-op post-open.\n settleOpen(new Error(\"WebSocket errored before it opened\"));\n },\n });\n\n this.transport.connect();\n\n try {\n return await openPromise;\n } catch (err) {\n this.config.logger.warn(\"WebSocket open failed, falling back to HTTP\", err);\n // Stop the transport so a late onOpen or auto-reconnect doesn't fire\n // against a client that has already committed to HTTP mode.\n this.transport?.disconnect();\n this.transport = null;\n const initialState = await fetchInitialState(liveUrl, this.config.logger);\n return { usingWebSocket: false, initialState };\n }\n }\n\n disconnect(): void {\n this.transport?.disconnect();\n this.transport = null;\n }\n\n send(data: string): void {\n this.transport?.send(data);\n }\n\n getReadyState(): number | undefined {\n return this.transport?.getSocket()?.readyState;\n }\n\n getSocket(): WebSocket | null {\n return this.transport?.getSocket() ?? null;\n }\n\n private getWebSocketUrl(): string {\n const liveUrl = this.liveUrlOverride || this.config.options.liveUrl || \"/live\";\n const baseUrl = this.config.options.wsUrl;\n if (baseUrl) {\n return baseUrl;\n }\n const wsScheme = window.location.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n return `${wsScheme}//${window.location.host}${liveUrl}`;\n }\n\n private getLiveUrl(): string {\n return (\n this.liveUrlOverride ||\n this.config.options.liveUrl ||\n window.location.pathname + window.location.search\n );\n }\n}\n\nexport async function checkWebSocketAvailability(\n liveUrl: string,\n logger?: Logger\n): Promise<boolean> {\n try {\n const response = await fetch(liveUrl, {\n method: \"HEAD\",\n });\n\n const wsHeader = response.headers.get(\"X-LiveTemplate-WebSocket\");\n if (wsHeader) {\n return wsHeader === \"enabled\";\n }\n\n return true;\n } catch (error) {\n logger?.warn(\"Failed to check WebSocket availability:\", error);\n return true;\n }\n}\n\nexport async function fetchInitialState(\n liveUrl: string,\n logger?: Logger\n): Promise<UpdateResponse | null> {\n try {\n const response = await fetch(liveUrl, {\n method: \"GET\",\n credentials: \"include\",\n headers: {\n Accept: \"application/json\",\n },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to fetch initial state: ${response.status}`);\n }\n\n return (await response.json()) as UpdateResponse;\n } catch (error) {\n logger?.warn(\"Failed to fetch initial state:\", error);\n return null;\n }\n}\n", "/**\n * S3Uploader - Handles direct uploads to S3 using presigned URLs\n */\n\nimport type {\n ExternalUploadMeta,\n UploadEntry,\n UploadProgressCallback,\n Uploader,\n} from \"./types\";\n\nexport class S3Uploader implements Uploader {\n /**\n * Upload a file directly to S3 using presigned PUT URL\n */\n async upload(\n entry: UploadEntry,\n meta: ExternalUploadMeta,\n onProgress?: UploadProgressCallback\n ): Promise<void> {\n const { file } = entry;\n\n // Create abort controller for cancellation\n entry.abortController = new AbortController();\n\n try {\n // Create XMLHttpRequest for progress tracking\n const xhr = new XMLHttpRequest();\n\n // Track upload progress and notify handler\n xhr.upload.addEventListener(\"progress\", (e) => {\n if (e.lengthComputable) {\n entry.bytesUploaded = e.loaded;\n entry.progress = Math.round((e.loaded / e.total) * 100);\n // Notify progress callback\n if (onProgress) {\n onProgress(entry);\n }\n }\n });\n\n // Handle abort\n entry.abortController.signal.addEventListener(\"abort\", () => {\n xhr.abort();\n });\n\n // Create promise for upload completion\n const uploadPromise = new Promise<void>((resolve, reject) => {\n xhr.addEventListener(\"load\", () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n entry.done = true;\n entry.progress = 100;\n resolve();\n } else {\n reject(new Error(`S3 upload failed with status ${xhr.status}: ${xhr.statusText}`));\n }\n });\n\n xhr.addEventListener(\"error\", () => {\n reject(new Error(\"S3 upload failed: Network error\"));\n });\n\n xhr.addEventListener(\"abort\", () => {\n reject(new Error(\"S3 upload cancelled\"));\n });\n });\n\n // Open connection\n xhr.open(\"PUT\", meta.url);\n\n // Set headers\n if (meta.headers) {\n for (const [key, value] of Object.entries(meta.headers)) {\n xhr.setRequestHeader(key, value);\n }\n }\n\n // Send file\n xhr.send(file);\n\n await uploadPromise;\n } catch (error) {\n entry.error = error instanceof Error ? error.message : String(error);\n throw error;\n }\n }\n}\n", "/**\n * UploadHandler - Manages file uploads for LiveTemplate\n *\n * Handles:\n * - File input detection and change events\n * - Chunked uploads to server via WebSocket\n * - External uploads (S3) via presigned URLs\n * - Progress tracking and callbacks\n * - Upload cancellation\n */\n\nimport { S3Uploader } from \"./s3-uploader\";\nimport type {\n ExternalUploadMeta,\n FileMetadata,\n UploadChunkMessage,\n UploadCompleteMessage,\n UploadEntry,\n UploadHandlerOptions,\n UploadProgressMessage,\n UploadStartMessage,\n UploadStartResponse,\n Uploader,\n} from \"./types\";\n\nexport class UploadHandler {\n private entries: Map<string, UploadEntry> = new Map();\n private pendingFiles: Map<string, File[]> = new Map(); // uploadName -> files\n private autoUploadConfig: Map<string, boolean> = new Map(); // uploadName -> autoUpload\n private chunkSize: number;\n private uploaders: Map<string, Uploader> = new Map();\n private onProgress?: (entry: UploadEntry) => void;\n private onComplete?: (uploadName: string, entries: UploadEntry[]) => void;\n private onError?: (entry: UploadEntry, error: string) => void;\n private inputHandlers: WeakMap<HTMLInputElement, EventListener> = new WeakMap();\n\n constructor(\n private sendMessage: (message: any) => void,\n options: UploadHandlerOptions = {}\n ) {\n this.chunkSize = options.chunkSize || 256 * 1024; // 256KB default\n this.onProgress = options.onProgress;\n this.onComplete = options.onComplete;\n this.onError = options.onError;\n\n // Register default uploaders\n this.uploaders.set(\"s3\", new S3Uploader());\n }\n\n /**\n * Initialize upload detection on file inputs with lvt-upload attribute\n */\n initializeFileInputs(container: Element) {\n const inputs = container.querySelectorAll<HTMLInputElement>(\n 'input[type=\"file\"][lvt-upload]'\n );\n\n inputs.forEach((input) => {\n const uploadName = input.getAttribute(\"lvt-upload\");\n if (!uploadName) return;\n\n // Remove existing listener if any\n const existingHandler = this.inputHandlers.get(input);\n if (existingHandler) {\n input.removeEventListener(\"change\", existingHandler);\n }\n\n // Create new handler\n const handler = (e: Event) => {\n const files = (e.target as HTMLInputElement).files;\n if (!files || files.length === 0) return;\n\n // Always send upload_start to get validation and config\n // But only proceed with chunks if autoUpload is true\n this.startUpload(uploadName, Array.from(files));\n };\n\n input.addEventListener(\"change\", handler);\n // Store handler in WeakMap to prevent memory leaks\n this.inputHandlers.set(input, handler);\n });\n }\n\n /**\n * Start upload process for selected files\n */\n async startUpload(uploadName: string, files: File[]): Promise<void> {\n // Store files temporarily for when server response arrives\n this.pendingFiles.set(uploadName, files);\n\n // Create file metadata\n const fileMetadata: FileMetadata[] = files.map((file) => ({\n name: file.name,\n type: file.type || \"application/octet-stream\",\n size: file.size,\n }));\n\n // Send upload_start message to server\n const startMessage: UploadStartMessage = {\n action: \"upload_start\",\n upload_name: uploadName,\n files: fileMetadata,\n };\n\n this.sendMessage(startMessage);\n }\n\n /**\n * Handle upload_start response from server\n */\n async handleUploadStartResponse(\n response: UploadStartResponse\n ): Promise<void> {\n const { upload_name, entries: entryInfos } = response;\n\n // Store autoUpload configuration from first entry\n if (entryInfos.length > 0) {\n this.autoUploadConfig.set(upload_name, entryInfos[0].auto_upload);\n }\n\n // Get pending files for this upload\n const files = this.pendingFiles.get(upload_name);\n if (!files) {\n console.error(`No pending files found for upload: ${upload_name}`);\n return;\n }\n\n // Clear pending files\n this.pendingFiles.delete(upload_name);\n\n // Build a map from file name to file object for lookup\n const fileMap = new Map<string, File>();\n for (const file of files) {\n fileMap.set(file.name, file);\n }\n\n // Create upload entries\n const entries: UploadEntry[] = [];\n\n for (const info of entryInfos) {\n const file = fileMap.get(info.client_name);\n\n if (!file) {\n console.warn(\n `No file found for entry ${info.entry_id} (client_name: ${info.client_name})`\n );\n continue;\n }\n\n const entry: UploadEntry = {\n id: info.entry_id,\n file,\n uploadName: upload_name,\n progress: 0,\n bytesUploaded: 0,\n valid: info.valid,\n done: false,\n error: info.error,\n external: info.external,\n };\n\n this.entries.set(entry.id, entry);\n entries.push(entry);\n\n // Handle invalid entries\n if (!info.valid) {\n if (this.onError && info.error) {\n this.onError(entry, info.error);\n }\n continue;\n }\n\n // Only start upload immediately if autoUpload is true\n // Otherwise, entries are stored and will be uploaded on form submit\n if (info.auto_upload) {\n // Start upload (external or chunked)\n if (info.external) {\n this.uploadExternal(entry, info.external);\n } else {\n this.uploadChunked(entry);\n }\n }\n }\n }\n\n /**\n * Upload file using external uploader (S3, etc.)\n */\n private async uploadExternal(\n entry: UploadEntry,\n meta: ExternalUploadMeta\n ): Promise<void> {\n try {\n const uploader = this.uploaders.get(meta.uploader);\n if (!uploader) {\n throw new Error(`Unknown uploader: ${meta.uploader}`);\n }\n\n // Start external upload with progress callback\n await uploader.upload(entry, meta, this.onProgress);\n\n // Notify server of completion\n const completeMessage: UploadCompleteMessage = {\n action: \"upload_complete\",\n upload_name: entry.uploadName,\n entry_ids: [entry.id],\n };\n\n this.sendMessage(completeMessage);\n\n if (this.onComplete) {\n this.onComplete(entry.uploadName, [entry]);\n }\n\n // Clear the file input to prevent re-upload of the same file\n this.clearFileInput(entry.uploadName);\n\n // Schedule cleanup after completion\n this.cleanupEntries(entry.uploadName);\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n entry.error = errorMsg;\n if (this.onError) {\n this.onError(entry, errorMsg);\n }\n // Schedule cleanup after error\n this.cleanupEntries(entry.uploadName);\n }\n }\n\n /**\n * Upload file in chunks via WebSocket\n */\n private async uploadChunked(entry: UploadEntry): Promise<void> {\n const { file, id } = entry;\n let offset = 0;\n\n // Create abort controller for cancellation\n entry.abortController = new AbortController();\n\n try {\n while (offset < file.size) {\n // Check if upload was cancelled\n if (entry.abortController.signal.aborted) {\n throw new Error(\"Upload cancelled\");\n }\n // Read chunk\n const end = Math.min(offset + this.chunkSize, file.size);\n const chunk = file.slice(offset, end);\n\n // Convert to base64\n const base64 = await this.fileToBase64(chunk);\n\n // Send chunk\n const chunkMessage: UploadChunkMessage = {\n action: \"upload_chunk\",\n entry_id: id,\n chunk_base64: base64,\n offset,\n total: file.size,\n };\n\n this.sendMessage(chunkMessage);\n\n // Update progress\n offset = end;\n entry.bytesUploaded = offset;\n entry.progress = Math.round((offset / file.size) * 100);\n\n if (this.onProgress) {\n this.onProgress(entry);\n }\n }\n\n // Send complete message\n entry.done = true;\n const completeMessage: UploadCompleteMessage = {\n action: \"upload_complete\",\n upload_name: entry.uploadName,\n entry_ids: [id],\n };\n\n this.sendMessage(completeMessage);\n\n if (this.onComplete) {\n this.onComplete(entry.uploadName, [entry]);\n }\n\n // Clear the file input to prevent re-upload of the same file\n this.clearFileInput(entry.uploadName);\n\n // Schedule cleanup after completion\n this.cleanupEntries(entry.uploadName);\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n entry.error = errorMsg;\n if (this.onError) {\n this.onError(entry, errorMsg);\n }\n // Schedule cleanup after error\n this.cleanupEntries(entry.uploadName);\n }\n }\n\n /**\n * Handle progress message from server (for chunked uploads)\n */\n handleProgressMessage(message: UploadProgressMessage): void {\n const entry = this.entries.get(message.entry_id);\n if (!entry) return;\n\n entry.progress = message.progress;\n entry.bytesUploaded = message.bytes_recv;\n\n if (this.onProgress) {\n this.onProgress(entry);\n }\n }\n\n /**\n * Cancel upload\n */\n cancelUpload(entryId: string): void {\n const entry = this.entries.get(entryId);\n if (!entry) return;\n\n // Abort external upload if in progress\n if (entry.abortController) {\n entry.abortController.abort();\n }\n\n // Send cancel message to server\n this.sendMessage({\n action: \"cancel_upload\",\n entry_id: entryId,\n });\n\n // Remove entry\n this.entries.delete(entryId);\n }\n\n /**\n * Get all entries for an upload name\n */\n getEntries(uploadName: string): UploadEntry[] {\n const entries: UploadEntry[] = [];\n for (const entry of this.entries.values()) {\n if (entry.uploadName === uploadName) {\n entries.push(entry);\n }\n }\n return entries;\n }\n\n /**\n * Trigger upload for all pending entries (used when autoUpload is false)\n * Called by LiveTemplate client on form submit\n */\n triggerPendingUploads(uploadName: string): void {\n // Get all entries for this upload that haven't started yet\n const pendingEntries: UploadEntry[] = [];\n for (const entry of this.entries.values()) {\n if (\n entry.uploadName === uploadName &&\n entry.progress === 0 &&\n !entry.done &&\n !entry.error\n ) {\n pendingEntries.push(entry);\n }\n }\n\n // Start uploads\n for (const entry of pendingEntries) {\n if (entry.external) {\n this.uploadExternal(entry, entry.external);\n } else {\n this.uploadChunked(entry);\n }\n }\n }\n\n /**\n * Register custom uploader\n */\n registerUploader(name: string, uploader: Uploader): void {\n this.uploaders.set(name, uploader);\n }\n\n /**\n * Clear file input to prevent re-upload of the same file\n * Called after successful upload completion\n */\n private clearFileInput(uploadName: string): void {\n // Find all file inputs with this upload name\n const inputs = document.querySelectorAll<HTMLInputElement>(\n `input[type=\"file\"][lvt-upload=\"${uploadName}\"]`\n );\n\n inputs.forEach((input) => {\n // Clear the file input value\n input.value = '';\n });\n }\n\n /**\n * Clean up completed or errored upload entries to prevent memory leaks\n * Automatically called after completion/error, but can be called manually\n * @param uploadName - Optional upload name to clean specific uploads\n * @param delay - Optional delay in ms before cleanup (default: 5000ms)\n */\n cleanupEntries(uploadName?: string, delay: number = 5000): void {\n setTimeout(() => {\n const entriesToRemove: string[] = [];\n\n for (const [id, entry] of this.entries) {\n // Skip if uploadName is specified and doesn't match\n if (uploadName && entry.uploadName !== uploadName) continue;\n\n // Remove completed or errored entries\n if (entry.done || entry.error) {\n entriesToRemove.push(id);\n }\n }\n\n for (const id of entriesToRemove) {\n this.entries.delete(id);\n }\n }, delay);\n }\n\n /**\n * Convert File/Blob to base64 string\n */\n private fileToBase64(blob: Blob): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => {\n const result = reader.result as string;\n // Remove data URL prefix\n const base64 = result.split(\",\")[1];\n resolve(base64);\n };\n reader.onerror = reject;\n reader.readAsDataURL(blob);\n });\n }\n}\n", "export type LogLevel = \"silent\" | \"error\" | \"warn\" | \"info\" | \"debug\";\n\nconst levelPriority: Record<LogLevel, number> = {\n silent: 0,\n error: 1,\n warn: 2,\n info: 3,\n debug: 4,\n};\n\ninterface LogState {\n level: LogLevel;\n}\n\ntype ConsoleMethod = \"error\" | \"warn\" | \"info\" | \"debug\";\n\nconst DEFAULT_SCOPE = \"LiveTemplate\";\n\n/**\n * Lightweight console logger with support for log levels and scoped prefixes.\n */\nexport class Logger {\n constructor(\n private readonly state: LogState,\n private readonly scope: string[] = [],\n private readonly sink: Console = console\n ) {}\n\n setLevel(level: LogLevel): void {\n this.state.level = level;\n }\n\n getLevel(): LogLevel {\n return this.state.level;\n }\n\n child(scope: string): Logger {\n return new Logger(this.state, [...this.scope, scope], this.sink);\n }\n\n isDebugEnabled(): boolean {\n return this.shouldLog(\"debug\");\n }\n\n error(...args: unknown[]): void {\n this.log(\"error\", \"error\", args);\n }\n\n warn(...args: unknown[]): void {\n this.log(\"warn\", \"warn\", args);\n }\n\n info(...args: unknown[]): void {\n this.log(\"info\", \"info\", args);\n }\n\n debug(...args: unknown[]): void {\n this.log(\"debug\", \"debug\", args);\n }\n\n private log(level: LogLevel, method: ConsoleMethod, args: unknown[]): void {\n if (!this.shouldLog(level)) {\n return;\n }\n\n const target =\n (this.sink[method] as (...args: unknown[]) => void) ||\n (console[method] as (...args: unknown[]) => void) ||\n console.log;\n target.apply(this.sink, [this.formatPrefix(), ...args]);\n }\n\n private shouldLog(level: LogLevel): boolean {\n return levelPriority[level] <= levelPriority[this.state.level];\n }\n\n private formatPrefix(): string {\n if (this.scope.length === 0) {\n return `[${DEFAULT_SCOPE}]`;\n }\n\n return `[${DEFAULT_SCOPE}:${this.scope.join(\":\")}]`;\n }\n}\n\nexport interface LoggerOptions {\n level?: LogLevel;\n scope?: string | string[];\n sink?: Console;\n}\n\nexport function createLogger(options: LoggerOptions = {}): Logger {\n const state: LogState = {\n level: options.level ?? \"info\",\n };\n\n const scope = Array.isArray(options.scope)\n ? options.scope\n : options.scope\n ? [options.scope]\n : [];\n\n return new Logger(state, scope, options.sink ?? console);\n}\n", "import type { LiveTemplateClient } from \"../livetemplate-client\";\nimport type { UpdateResult } from \"../types\";\n\n/**\n * Utility function to load and apply updates from JSON files.\n */\nexport async function loadAndApplyUpdate(\n client: LiveTemplateClient,\n updatePath: string\n): Promise<UpdateResult> {\n try {\n const nodeRequire = (globalThis as any)?.require;\n if (typeof nodeRequire === \"function\") {\n const fs = nodeRequire(\"fs\");\n const updateData = JSON.parse(fs.readFileSync(updatePath, \"utf8\"));\n return client.applyUpdate(updateData);\n }\n\n const response = await fetch(updatePath);\n const updateData = await response.json();\n return client.applyUpdate(updateData);\n } catch (error) {\n throw new Error(`Failed to load update from ${updatePath}: ${error}`);\n }\n}\n\n/**\n * Compare two HTML strings, ignoring whitespace differences.\n */\nexport function compareHTML(\n expected: string,\n actual: string\n): {\n match: boolean;\n differences: string[];\n} {\n const differences: string[] = [];\n\n const normalizeHTML = (html: string) => {\n return html.replace(/\\s+/g, \" \").replace(/>\\s+</g, \"><\").trim();\n };\n\n const normalizedExpected = normalizeHTML(expected);\n const normalizedActual = normalizeHTML(actual);\n\n if (normalizedExpected === normalizedActual) {\n return { match: true, differences: [] };\n }\n\n const expectedLines = normalizedExpected.split(\"\\n\");\n const actualLines = normalizedActual.split(\"\\n\");\n const maxLines = Math.max(expectedLines.length, actualLines.length);\n\n for (let i = 0; i < maxLines; i++) {\n const expectedLine = expectedLines[i] || \"\";\n const actualLine = actualLines[i] || \"\";\n\n if (expectedLine !== actualLine) {\n differences.push(`Line ${i + 1}:`);\n differences.push(` Expected: ${expectedLine}`);\n differences.push(` Actual: ${actualLine}`);\n }\n }\n\n return { match: false, differences };\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["livetemplate_client_exports", "__export", "LiveTemplateClient", "compareHTML", "loadAndApplyUpdate", "setupReactiveAttributeListeners", "DOCUMENT_FRAGMENT_NODE", "morphAttrs", "fromNode", "toNode", "toNodeAttrs", "attr", "attrName", "attrNamespaceURI", "attrValue", "fromValue", "i", "fromNodeAttrs", "range", "NS_XHTML", "doc", "HAS_TEMPLATE_SUPPORT", "HAS_RANGE_SUPPORT", "createFragmentFromTemplate", "str", "template", "createFragmentFromRange", "fragment", "createFragmentFromWrap", "toElement", "compareNodeNames", "fromEl", "toEl", "fromNodeName", "toNodeName", "fromCodeStart", "toCodeStart", "createElementNS", "name", "namespaceURI", "moveChildren", "curChild", "nextChild", "syncBooleanAttrProp", "specialElHandlers", "parentNode", "parentName", "newValue", "firstChild", "oldValue", "selectedIndex", "optgroup", "nodeName", "ELEMENT_NODE", "DOCUMENT_FRAGMENT_NODE$1", "TEXT_NODE", "COMMENT_NODE", "noop", "defaultGetNodeKey", "node", "morphdomFactory", "options", "toNodeHtml", "getNodeKey", "onBeforeNodeAdded", "onNodeAdded", "onBeforeElUpdated", "onElUpdated", "onBeforeNodeDiscarded", "onNodeDiscarded", "onBeforeElChildrenUpdated", "skipFromChildren", "addChild", "parent", "child", "childrenOnly", "fromNodesLookup", "keyedRemovalList", "addKeyedRemoval", "key", "walkDiscardedChildNodes", "skipKeyedNodes", "removeNode", "indexTree", "handleNodeAdded", "el", "nextSibling", "unmatchedFromEl", "morphEl", "cleanupFromEl", "curFromNodeChild", "curFromNodeKey", "fromNextSibling", "toElKey", "beforeUpdateResult", "morphChildren", "skipFrom", "curToNodeChild", "curToNodeKey", "toNextSibling", "matchingFromEl", "outer", "curFromNodeType", "isCompatible", "onBeforeNodeAddedResult", "specialElHandler", "morphedNode", "morphedNodeType", "toNodeType", "len", "elToRemove", "morphdom", "morphdom_esm_default", "FOCUSABLE_INPUTS", "FocusManager", "logger", "wrapper", "selector", "FOCUSABLE_INPUTS", "type", "wrapperId", "focusKey", "blurKey", "focusListener", "event", "_a", "target", "blurListener", "_b", "_c", "element", "index", "wasFocused", "selectionStart", "selectionEnd", "el", "LIFECYCLE_EVENTS", "LIFECYCLE_SET", "SYNTHETIC_TRIGGERS", "METHOD_MAP", "parseReactiveAttribute", "attrName", "attrValue", "newMatch", "methodKey", "action", "eventPart", "segments", "lastSegment", "lifecycle", "actionName", "resolveTarget", "element", "selector", "executeAction", "param", "classes", "c", "colonIndex", "name", "value", "matchesEvent", "binding", "processReactiveAttributes", "methodKeys", "selectorParts", "escapedAction", "m", "candidates", "e", "fallbackParts", "attr", "processElementInteraction", "trigger", "match", "isDOMEventTrigger", "setupReactiveAttributeListeners", "_a", "FX_LIFECYCLE_SET", "animatedElements", "parseFxTrigger", "attrName", "onMatch", "parts", "setupFxDOMEventTriggers", "scanRoot", "registryRoot", "registry", "fxListenersKey", "fxListeners", "entry", "processEl", "el", "_a", "attr", "parsed", "FX_LIFECYCLE_SET", "SYNTHETIC_TRIGGERS", "listenerKey", "effect", "attrNameCapture", "listener", "currentValue", "applyFxEffect", "teardownFxDOMEventTriggers", "rootElement", "listeners", "event", "handler", "guardKey", "processFxLifecycleAttributes", "lifecycle", "actionName", "htmlElement", "config", "computed", "duration", "color", "originalBackground", "originalTransition", "animatedElements", "animation", "animationValue", "rawBehavior", "behavior", "VALID_SCROLL_BEHAVIORS", "threshold", "mode", "setupFxLifecycleListeners", "e", "teardownFxLifecycleListeners", "handleScrollDirectives", "element", "handleHighlightDirectives", "handleAnimateDirectives", "ensureAnimateKeyframes", "style", "PENDING_PROCESSED_KEY", "handleToastDirectives", "trigger", "pending", "messages", "position", "stack", "getOrCreateToastStack", "msg", "createToastElement", "setupToastClickOutside", "key", "existing", "applyPositionStyles", "s", "inner", "t", "b", "btn", "debounce", "func", "wait", "timeout", "args", "context", "throttle", "limit", "inThrottle", "lvtSelector", "attr", "value", "escaped", "safeValue", "CLICK_AWAY_METHOD_MAP", "CLICK_AWAY_METHODS", "NON_BUBBLING", "DELEGATED_EVENT_TYPES", "EventDelegator", "context", "logger", "wrapperId", "eventType", "listenerKey", "existingListener", "button", "data", "attr", "key", "wrapperElement", "eventTypes", "rateLimitedHandlers", "listener", "e", "_a", "currentWrapper", "target", "element", "inWrapper", "attrName", "action", "actionElement", "isOrphanButton", "persistTable", "btn", "explicitAction", "submitter", "dialog", "keyFilter", "targetElement", "handleAction", "message", "formData", "checkboxes", "checkboxGroups", "cb", "group", "cbs", "name", "passwordFields", "el", "submitterForData", "actionFieldName", "value", "submitter2", "tier1FormData", "tier1FileInputs", "input", "submitButton", "originalButtonText", "uploadName", "dialogToClose", "throttleValue", "debounceValue", "handlerCache", "cacheKey", "callbackRefKey", "elementWithCallback", "rateLimitedHandler", "callLatest", "limit", "throttle", "wait", "debounce", "windowEvents", "lvtSelector", "clickAwaySelector", "m", "match", "method", "executeAction", "resolveTarget", "scanRoot", "delegatedKey", "delegated", "listenersKey", "allListeners", "entry", "root", "processEl", "triggers", "trigger", "isDOMEventTrigger", "processElementInteraction", "escaped", "triggerPattern", "handler", "hasMatch", "a", "listeners", "event", "guardKey", "getFocusableElements", "container", "selector", "htmlEl", "style", "isNotDisplayNone", "isNotVisibilityHidden", "hasLayoutContext", "focusTrapElements", "activeTrap", "trap", "htmlTrap", "focusableElements", "firstElement", "lastElement", "observerKey", "existingObserver", "processAutofocus", "htmlElement", "isVisible", "wasFocused", "observer", "mutations", "shouldProcess", "mutation", "node", "LinkInterceptor", "context", "logger", "wrapperId", "_a", "listenerKey", "existing", "wrapper", "listener", "e", "target", "currentWrapper", "prevHref", "link", "protocol", "href", "pushState", "_b", "_c", "targetURL", "refURL", "response", "html", "_ObserverManager", "context", "logger", "wrapperElement", "sentinel", "entries", "wrapper", "e", "detail", "ObserverManager", "LoadingIndicator", "bar", "style", "FormDisabler", "wrapper", "form", "input", "installed", "handleClick", "e", "el", "button", "targetId", "command", "target", "setupInvokerPolyfill", "deepClone", "obj", "isRangeNode", "node", "hasRangeAnywhere", "depth", "key", "child", "shouldFullReplace", "existing", "update", "TreeRenderer", "logger", "changed", "value", "oldValue", "newValue", "currentPath", "_a", "merged", "fieldPath", "isDifferentialOps", "existingIsRange", "rangeStructure", "operations", "statePath", "currentItems", "operation", "removeIndex", "updateIndex", "changes", "itemsToAdd", "itemsToPrepend", "targetIndex", "itemsToInsert", "newOrder", "reorderedItems", "itemsByKey", "item", "itemKey", "orderedKey", "html", "i", "staticSegment", "dynamicKey", "newStatePath", "fieldKey", "stateKey", "keys", "numericKeys", "k", "b", "itemStatePath", "idx", "rangeNode", "dynamics", "statics", "staticsMap", "elseKey", "elseStatePath", "hasStaticsMap", "itemIdx", "itemStatics", "localKey", "rangeData", "fieldValue", "items", "result", "prepend", "itemsArray", "keyPosStr", "FormLifecycleManager", "form", "button", "originalButtonText", "fieldset", "meta", "dialogParent", "ChangeAutoWirer", "context", "logger", "capabilities", "treeState", "prevSize", "wrapper", "el", "fieldName", "bindingType", "escapedName", "elements", "parentForm", "selects", "name", "cleanup", "node", "key", "child", "item", "statics", "i", "left", "right", "binding", "value", "text", "lastOpen", "lastClose", "partialTag", "match", "tagMatch", "typeMatch", "inputType", "element", "customDebounce", "parsed", "wait", "debouncedSend", "debounce", "eventType", "WebSocketTransport", "options", "socket", "_a", "_b", "event", "data", "_c", "_d", "_e", "maxAttempts", "baseDelay", "maxDelay", "exponentialDelay", "jitter", "delay", "WebSocketManager", "config", "liveUrl", "checkWebSocketAvailability", "fetchInitialState", "resolveOpen", "rejectOpen", "openPromise", "resolve", "reject", "settled", "hasConnected", "openTimeoutId", "settleOpen", "err", "payload", "error", "attempt", "baseUrl", "logger", "wsHeader", "response", "S3Uploader", "entry", "meta", "onProgress", "file", "xhr", "e", "uploadPromise", "resolve", "reject", "key", "value", "error", "UploadHandler", "sendMessage", "options", "S3Uploader", "container", "input", "uploadName", "existingHandler", "handler", "e", "files", "fileMetadata", "file", "startMessage", "response", "upload_name", "entryInfos", "fileMap", "entries", "info", "entry", "meta", "uploader", "completeMessage", "error", "errorMsg", "id", "offset", "end", "chunk", "base64", "chunkMessage", "message", "entryId", "pendingEntries", "name", "delay", "entriesToRemove", "blob", "resolve", "reject", "reader", "levelPriority", "DEFAULT_SCOPE", "Logger", "_Logger", "state", "scope", "sink", "level", "args", "method", "createLogger", "options", "_a", "_b", "loadAndApplyUpdate", "client", "updatePath", "nodeRequire", "fs", "updateData", "error", "compareHTML", "expected", "actual", "differences", "normalizeHTML", "html", "normalizedExpected", "normalizedActual", "expectedLines", "actualLines", "maxLines", "i", "expectedLine", "actualLine", "LiveTemplateClient", "_LiveTemplateClient", "options", "providedLogger", "logLevel", "debug", "restOptions", "resolvedLevel", "baseLogger", "createLogger", "TreeRenderer", "FocusManager", "FormLifecycleManager", "LoadingIndicator", "FormDisabler", "UploadHandler", "message", "entry", "uploadName", "entries", "error", "EventDelegator", "value", "form", "action", "formData", "button", "originalButtonText", "LinkInterceptor", "html", "href", "ObserverManager", "ChangeAutoWirer", "WebSocketManager", "_a", "_b", "_c", "response", "event", "autoInitLogger", "init", "wrapper", "client", "uploadMessage", "startResponse", "wrapperSelector", "connectionResult", "setupToastClickOutside", "setupReactiveAttributeListeners", "setupInvokerPolyfill", "setupFxLifecycleListeners", "teardownFxDOMEventTriggers", "teardownFxLifecycleListeners", "url", "flashParams", "hasFlashParams", "param", "readyState", "data", "seenKeys", "v", "k", "newLiveUrl", "liveUrl", "updateResponse", "doc", "oldId", "newTitleText", "newWrapper", "newId", "myEpoch", "n", "selector", "err", "body", "trimmed", "num", "update", "existingHTML", "result", "match", "innerContent", "newBodyContent", "element", "meta", "hasStaticsInTree", "node", "tempWrapper", "rawTag", "wrapTag", "root", "morphdom_esm_default", "fromEl", "toEl", "fromAttrs", "toElement", "i", "attr", "el", "handleScrollDirectives", "handleHighlightDirectives", "handleAnimateDirectives", "setupFxDOMEventTriggers", "handleToastDirectives", "hookName", "hookValue"]
7
7
  }